teeCopy a stream to the screen and to files at once
tee reads standard input and writes it both to standard output and to one or more files, like a T-shaped pipe fitting. You use it to watch a long command while keeping a log, to save the middle of a pipeline, and in the sudo tee idiom for writing to root-owned files.
tee [-a] [FILE]...You run a deploy by hand and want to see progress live while keeping a copy to attach to the change ticket.
./deploy.sh 2.14.2Any command whose output you want to both watch and keep.
tee is a tiny loop: it opens every file named on its command line (with O_TRUNC, or O_APPEND for -a), then repeatedly read()s a chunk from stdin and write()s the same bytes to stdout and to each file, until stdin hits end-of-file. Because tee opens the files itself, the permission check happens in tee's process. That's why sudo tee works where sudo echo ... > file fails: in the second case your own shell opens the file for the redirection before sudo even runs. tee only sees what arrives on its stdin, so stderr of the previous command bypasses it unless you add 2>&1. In a pipeline the shell's exit status is the last command's, which is tee's, and tee almost always succeeds. So a failing command piped into tee looks successful unless you use set -o pipefail or check PIPESTATUS.
-aAppend to the files instead of overwriting themFILE...Write a full copy to every file listed> /dev/nullSilence tee's stdout copy when you only want the file (sudo tee case)-iIgnore SIGINT, so Ctrl-C stops the producer but tee finishes writing-pKeep writing to the other outputs if one pipe reader exits (GNU)>(cmd)Process substitution: tee into another command, e.g. tee >(gzip > out.gz)sudo echo 'line' >> /etc/some.confThe redirection is performed by your unprivileged shell before sudo runs, so it fails with Permission denied.
echo 'line' | sudo tee -a /etc/some.conf > /dev/nullecho 'nameserver 10.0.0.2' | sudo tee /etc/resolv.conf (meant to add a line)Without -a, tee truncates the file: every existing line is gone.
echo 'nameserver 10.0.0.2' | sudo tee -a /etc/resolv.confmake build | tee build.log in CIThe pipeline's exit status is tee's, so a failed build passes, and stderr isn't in build.log either.
set -o pipefail; make build 2>&1 | tee build.logUsing tee to log a command that prints a lot with no newlines (progress bars)Programs often detect a pipe and change behaviour: no colours, block buffering, or carriage-return progress bars written into the log.
Use the tool's --no-progress / --quiet flags, or script -q -c 'cmd' log to keep a real terminalTry these on a Linux machine of your own. Each task has a hint and a solution.
Run ls -l /etc and both see the output and save it to ~/etc-listing.txt.
Append the line 'checked by devops' to ~/notes.txt using tee, without printing it on the screen.
Add the line '127.0.0.2 lab-test' to /etc/hosts using sudo.
Run 'ls /nope /etc' so that BOTH the error and the listing end up in ~/both.log while you watch.
teeCopy a stream to the screen and to files at once
tee reads standard input and writes it both to standard output and to one or more files, like a T-shaped pipe fitting. You use it to watch a long command while keeping a log, to save the middle of a pipeline, and in the sudo tee idiom for writing to root-owned files.
tee [-a] [FILE]...You run a deploy by hand and want to see progress live while keeping a copy to attach to the change ticket.
./deploy.sh 2.14.2Any command whose output you want to both watch and keep.
tee is a tiny loop: it opens every file named on its command line (with O_TRUNC, or O_APPEND for -a), then repeatedly read()s a chunk from stdin and write()s the same bytes to stdout and to each file, until stdin hits end-of-file. Because tee opens the files itself, the permission check happens in tee's process. That's why sudo tee works where sudo echo ... > file fails: in the second case your own shell opens the file for the redirection before sudo even runs. tee only sees what arrives on its stdin, so stderr of the previous command bypasses it unless you add 2>&1. In a pipeline the shell's exit status is the last command's, which is tee's, and tee almost always succeeds. So a failing command piped into tee looks successful unless you use set -o pipefail or check PIPESTATUS.
-aAppend to the files instead of overwriting themFILE...Write a full copy to every file listed> /dev/nullSilence tee's stdout copy when you only want the file (sudo tee case)-iIgnore SIGINT, so Ctrl-C stops the producer but tee finishes writing-pKeep writing to the other outputs if one pipe reader exits (GNU)>(cmd)Process substitution: tee into another command, e.g. tee >(gzip > out.gz)sudo echo 'line' >> /etc/some.confThe redirection is performed by your unprivileged shell before sudo runs, so it fails with Permission denied.
echo 'line' | sudo tee -a /etc/some.conf > /dev/nullecho 'nameserver 10.0.0.2' | sudo tee /etc/resolv.conf (meant to add a line)Without -a, tee truncates the file: every existing line is gone.
echo 'nameserver 10.0.0.2' | sudo tee -a /etc/resolv.confmake build | tee build.log in CIThe pipeline's exit status is tee's, so a failed build passes, and stderr isn't in build.log either.
set -o pipefail; make build 2>&1 | tee build.logUsing tee to log a command that prints a lot with no newlines (progress bars)Programs often detect a pipe and change behaviour: no colours, block buffering, or carriage-return progress bars written into the log.
Use the tool's --no-progress / --quiet flags, or script -q -c 'cmd' log to keep a real terminalTry these on a Linux machine of your own. Each task has a hint and a solution.
Run ls -l /etc and both see the output and save it to ~/etc-listing.txt.
Append the line 'checked by devops' to ~/notes.txt using tee, without printing it on the screen.
Add the line '127.0.0.2 lab-test' to /etc/hosts using sudo.
Run 'ls /nope /etc' so that BOTH the error and the listing end up in ~/both.log while you watch.