tee saves and passes on¶
Level: 101 · you want to watch a command's output and keep a copy of it
One line: tee file copies its stdin to stdout and to every file it is named, so cmd | tee file | next keeps a copy without breaking the pipe. It empties each file first unless you give it -a, and it copies only what reaches its stdin, which is why a build log needs 2>&1 before the pipe.
Measured¶
Verified output of tee_basics_sh.sh, identical on Linux and macOS — regenerated by tools/run_examples.py, never hand-typed.
$ printf "alma\ndarkfi\nnano\n" | tee names.txt | wc -l | tr -d " "
3
$ cat names.txt
alma
darkfi
nano
$ echo "first run" | tee run.log
first run
$ echo "second run" | tee run.log; echo "--- run.log:"; cat run.log
second run
--- run.log:
second run
$ echo "third run" | tee -a run.log > /dev/null; echo "--- run.log:"; cat run.log
--- run.log:
second run
third run
$ echo hello | tee a.txt b.txt c.txt; grep . a.txt b.txt c.txt
hello
a.txt:hello
b.txt:hello
c.txt:hello
$ printf "b\na\nb\n" | sort | tee after_sort.txt | uniq; echo "--- after_sort.txt:"; cat after_sort.txt
a
b
--- after_sort.txt:
a
b
b
$ build() { echo compiling; echo "warning: x is unused" >&2; echo done; }
$ build | tee build.log > /dev/null; echo "--- build.log:"; cat build.log
warning: x is unused
--- build.log:
compiling
done
$ build 2>&1 | tee build.log; echo "--- build.log:"; cat build.log
compiling
warning: x is unused
done
--- build.log:
compiling
warning: x is unused
done
- A T-junction.
wc -lcounted three lines, andnames.txtholds the same three: one stream, two destinations. - tee empties the file first. The second run replaced the first in
run.log.-aappended the third instead.> /dev/nullafterteekeeps the file and silences the copy on the screen. - Several files at once. One
tee a.txt b.txt c.txtwrote all three and printedhelloonce. - In the middle of a pipeline, tee records what one stage handed to the next:
after_sort.txtstill has the twoblines thatuniqmerged. - A pipe carries stdout only.
build | tee build.logloggedcompilinganddone. The warning went to stderr, pasttee, and never reached the log. With2>&1in front of the pipe, all three lines went throughtee, in the orderbuildwrote them.
A copy on the screen and a copy in a file is the everyday use: make 2>&1 | tee build.log lets you watch a long build and read it again afterwards. Checking whether that build failed is its own lesson, tee and the exit status.
On a Mac¶
Nothing changes. The key is shared: the BSD tee a Mac ships and GNU tee from coreutils 9.4 printed the same bytes for everything on this page.
In zsh and fish¶
tee is a program, not shell syntax, so it works the same everywhere. What differs is the shorthand for sending stderr down the pipe with stdout. zsh spells 2>&1 | as |&:
Verified output of tee_basics_zsh.zsh, identical on Linux and macOS — regenerated by tools/run_examples.py, never hand-typed.
$ printf "alma\ndarkfi\nnano\n" | tee names.txt | wc -l | tr -d " "
3
$ echo "third run" | tee -a run.log > /dev/null; echo "fourth run" | tee -a run.log > /dev/null; cat run.log
third run
fourth run
$ build() { echo compiling; echo "warning: x is unused" >&2; echo done }
$ build |& tee build.log; echo "--- build.log:"; cat build.log
compiling
warning: x is unused
done
--- build.log:
compiling
warning: x is unused
done
fish spells it &|, and a function's stderr still goes past tee unless you send it down the pipe:
Verified output of tee_basics_fish.fish, identical on Linux and macOS — regenerated by tools/run_examples.py, never hand-typed.
$ printf "alma\ndarkfi\nnano\n" | tee names.txt | wc -l | tr -d " "
3
$ echo "third run" | tee -a run.log > /dev/null; echo "fourth run" | tee -a run.log > /dev/null; cat run.log
third run
fourth run
$ function build; echo compiling; echo "warning: x is unused" >&2; echo done; end
$ build | tee build.log > /dev/null; echo "--- build.log:"; cat build.log
warning: x is unused
--- build.log:
compiling
done
$ build 2>&1 | tee build.log; echo "--- build.log:"; cat build.log
compiling
warning: x is unused
done
--- build.log:
compiling
warning: x is unused
done
$ build &| tee build.log; echo "--- build.log:"; cat build.log
compiling
warning: x is unused
done
--- build.log:
compiling
warning: x is unused
done
| bash | zsh | fish | |
|---|---|---|---|
| save and see | cmd \| tee f |
cmd \| tee f |
cmd \| tee f |
| stderr into the log too | cmd 2>&1 \| tee f |
cmd 2>&1 \| tee f, cmd \|& tee f |
cmd 2>&1 \| tee f, cmd &\| tee f |
If you are coming from another library¶
- Encodings.
split,paste,lookandtee↗ usesteeas the control: a NUL byte and a byte that cannot appear in UTF-8 go through it untouched, which is what makes… | tee raw | …the place to look when a pipeline mangles its input. - Python. Standard in, standard out, and pipes ↗ outlines why a Python program's output can reach
teein blocks rather than line by line: stdout is buffered differently when it is a pipe.
See also¶
- tee and the exit status — what
| tee logdoes to$? - sudo tee writes where redirection cannot —
tee -aas a way to write a file you do not own - tee into several commands — one stream into more than one command
- stdout and stderr go separately —
2>&1, and why it goes before the pipe - POSIX, tee ↗ —
-aand-i, all a BSDteehas - GNU coreutils, tee invocation ↗