tee — Display Output and Write It to Files at Once

Practical guide to tee — read from standard input and write to standard output and one or more files at once. Ideal for logging output in pipelines.

tee reads from standard input and writes the result to standard output and one or more files at the same time – like a T-junction in a pipe that branches off the data stream. That lets you watch a command's output live on screen while also logging it to a file, without breaking the pipeline. By default tee overwrites the target file; with -a you append instead. Its best-known trick is the sudo tee pattern: because a > redirect is performed by the shell, not by sudo, … | sudo tee file is the clean way to write to root-owned files.

Basic Usage

<command> | tee <file> — Write command output to both the screen and a file (overwrites file).

ls -la | tee listing.txt

<command> | tee -a <file> — Append output to a file instead of overwriting.

echo 'new entry' | tee -a log.txt

<command> | tee <file1> <file2> — Write output to multiple files simultaneously.

date | tee log1.txt log2.txt backup.txt

<command> | tee /dev/stderr — Duplicate output to stderr (useful for logging in pipelines).

curl -s api.example.com | tee /dev/stderr | jq '.data'

Pipeline Patterns

<cmd1> | tee <file> | <cmd2> — Save intermediate output while continuing the pipeline.

cat data.csv | tee raw-backup.csv | sort -t',' -k2 > sorted.csv

<cmd1> | tee >(cmd2) | <cmd3> — Send output to a command via process substitution AND continue the pipeline.

ls -la | tee >(grep '.log' > logs.txt) | wc -l

<cmd1> | tee >(cmd2) >(cmd3) > /dev/null — Fan out output to multiple commands without terminal display.

cat data.txt | tee >(wc -l > count.txt) >(grep ERROR > errors.txt) > /dev/null

<command> 2>&1 | tee <file> — Capture both stdout and stderr to a file and display.

make build 2>&1 | tee build.log

Writing with Elevated Privileges

echo '<text>' | sudo tee <file> — Write to a root-owned file (sudo redirect doesn't work with > alone).

echo 'nameserver 8.8.8.8' | sudo tee /etc/resolv.conf

echo '<text>' | sudo tee -a <file> — Append to a root-owned file.

echo '192.168.1.100 myserver' | sudo tee -a /etc/hosts

echo '<text>' | sudo tee <file> > /dev/null — Write to a root-owned file without echoing to the screen.

echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swap.conf > /dev/null

cat <source> | sudo tee <dest> > /dev/null — Copy a file to a root-owned location.

cat nginx.conf | sudo tee /etc/nginx/sites-available/mysite > /dev/null

Logging & Debugging

<script> 2>&1 | tee -a <logfile> — Run a script and log all output (stdout + stderr) with append.

./deploy.sh 2>&1 | tee -a /var/log/deploy.log

script -q /dev/null <command> | tee <file> — Capture output from commands that detect non-terminal output.

script -q /dev/null ls --color | tee colored-output.txt

<command> | tee >(logger -t <tag>) — Send pipeline output to syslog via process substitution.

backup.sh 2>&1 | tee >(logger -t backup)

<command> | tee >(ts '[%Y-%m-%d %H:%M:%S]' >> <logfile>) — Add timestamps to logged output using ts (from moreutils).

tail -f /var/log/app.log | tee >(ts '[%Y-%m-%d %H:%M:%S]' >> timestamped.log)

Common Use Cases

tar czf - <dir> | tee <archive> | md5sum — Create an archive and calculate its checksum in one pass.

tar czf - /var/www | tee backup.tar.gz | md5sum > backup.md5

<command> | tee /dev/tty | <next_command> — Display output on the terminal while piping it to the next command.

find . -name '*.log' | tee /dev/tty | xargs rm

echo '<config>' | tee <file1> <file2> <file3> > /dev/null — Write the same content to multiple config files at once.

echo 'export NODE_ENV=production' | tee .env .env.local > /dev/null

diff <(command1) <(command2) | tee diff-output.txt — Compare two command outputs and save the diff.

diff <(curl -s api.example.com/v1) <(curl -s api.example.com/v2) | tee api-diff.txt

cat <<'EOF' | sudo tee <file> > /dev/null\n<content>\nEOF — Write a multi-line heredoc to a root-owned file.

cat <<'EOF' | sudo tee /etc/nginx/conf.d/app.conf > /dev/null
server {
    listen 80;
    server_name example.com;
}
EOF

Conclusion

tee bridges the gap between "see it on screen" and "write it to a file" – indispensable for logging build and deploy runs, writing to root-owned files (sudo tee), and fanning a stream out to several destinations via process substitution. Mind two things: tee overwrites without warning if you forget -a – for logging, -a is almost always the right choice. And to capture error messages too, redirect stderr onto stdout first with 2>&1, because tee only sees the stdout stream of the pipe. In a pipeline the exit status comes from the last command by default, which is tee itself; if you need the status of the actual command, set set -o pipefail.

Further Reading

  • xargs – turn input into arguments for further commands
  • tail – follow log files live, often the counterpart to tee logs
  • less – page through large output and log files