Linux Basic — Writing with tee
Writing with tee
tee reads from standard input and writes to both standard output and one or more files at the same time. It is the answer to three very common scripting needs:
- See output while logging it
- Write to a file that needs
sudofrom inside a pipeline - Fan-out one stream to many destinations
The Basics
echo "hello" | tee greet.txt
# hello ← still goes to your terminal
cat greet.txt
# hello ← AND it's in the file
tee writes to the file and re-emits everything it received on its own stdout. The next command in a pipeline (or your terminal) gets the data unchanged.
Append Instead of Overwrite — -a
By default tee truncates the target file. Use -a for append:
echo "first" | tee log.txt # truncates each time
echo "second" | tee log.txt
cat log.txt
# second ← "first" is gone
echo "first" | tee -a log.txt
echo "second" | tee -a log.txt
cat log.txt
# first
# second
Same > vs >> distinction you saw with regular redirection.
See Output While Logging It
The single most common reason to reach for tee:
# Build and watch the output live — but also save it for later
./build.sh 2>&1 | tee build.log
| What | Without tee |
With tee |
|---|---|---|
| See output now | yes, if you redirect to terminal | yes |
| Save output | no | yes |
| Both at once | no | yes |
Without tee you have to choose: either ./build.sh (see live, no record) or ./build.sh > build.log (record, but silent terminal). tee gives you both.
To pipe further while still recording:
./build.sh 2>&1 | tee build.log | grep -E 'WARN|ERROR'
# WARNING: deprecated flag
# ERROR: missing artefact
Write to a Root-Owned File — sudo tee
sudo raises privileges for the command, not for the shell’s redirection:
# ❌ Does NOT work — the shell does the > before sudo runs
sudo echo "127.0.0.1 myhost" > /etc/hosts
# bash: /etc/hosts: Permission denied
# ✅ Pipe to sudo tee, discard stdout (you already have the content)
echo "127.0.0.1 myhost" | sudo tee -a /etc/hosts > /dev/null
| Want to | Use |
|---|---|
| Overwrite (root) | cmd \| sudo tee /file > /dev/null |
| Append (root) | cmd \| sudo tee -a /file > /dev/null |
| See it too | drop the > /dev/null |
# Write a multi-line config with sudo
sudo tee /etc/myapp.conf > /dev/null <<'EOF'
listen = 0.0.0.0:80
workers = 4
EOF
Fan-Out — Write to Many Files
date | tee a.txt b.txt c.txt
# Sun 1 Jun 2026 00:00:00 +0530
cat a.txt b.txt c.txt
# Sun 1 Jun 2026 00:00:00 +0530
# Sun 1 Jun 2026 00:00:00 +0530
# Sun 1 Jun 2026 00:00:00 +0530
All three files get the same content. Append form: tee -a a.txt b.txt c.txt.
Fan-Out to Other Commands — Process Substitution
The most powerful trick: combine tee with process substitution (>(cmd)) so each “destination” is another process, not just a file.
# Build, log it AND show only errors, AND check it AND zip a copy
./build.sh 2>&1 \
| tee build.log \
>(grep -E 'WARN|ERROR' > issues.log) \
>(sha256sum > build.log.sha256) \
>(gzip > build.log.gz)
What that does:
build.log— full output (tee’s normal target)>(grep ...)— filtered errors saved toissues.log>(sha256sum)— checksum of the build output>(gzip)— compressed copy
All four destinations get a copy of every byte, in real time, with one tee invocation.
Process substitution is covered in detail in Advanced Scripting → Process Substitution.
Buffering — Why You Sometimes See tee “Hang”
Pipelines are line- or block-buffered by default. If you run:
my_slow_program | tee out.log
…and my_slow_program produces output a byte at a time, you may not see it appear in real time. The fix depends on the source:
- For Python:
python -u script.py(unbuffered) - For most tools:
stdbuf -oL my_slow_program | tee out.log(line-buffered) - For
grep:grep --line-buffered ...
tee itself flushes after each input chunk — it does not introduce extra buffering.
When NOT to Use tee
If you don’t need both destinations, plain redirection is simpler and cheaper:
# ❌ Overkill
echo "hi" | tee out.txt > /dev/null
# ✅ Simpler
echo "hi" > out.txt
Reach for tee when at least two destinations are needed.
Comparison: >> vs tee -a
| Feature | cmd >> file |
cmd \| tee -a file |
|---|---|---|
| Append to file | ✅ | ✅ |
| See output too | ❌ | ✅ |
Works with sudo |
❌ on the shell side | ✅ |
| Multiple files at once | ❌ | ✅ |
| Pipe further downstream | ❌ | ✅ |
| Performance overhead | none | one extra process |
Use >> for “just save it”; use tee -a whenever you need any of the extras.
Common Mistakes
| ❌ Wrong | ✅ Right | Issue |
|---|---|---|
sudo echo x > /etc/f |
echo x \| sudo tee /etc/f >/dev/null |
sudo doesn’t elevate > |
cmd \| tee file (overwrites every run) |
cmd \| tee -a file |
forgot -a |
cmd \| tee file \| cmd2 then expects cmd2 to read only filtered |
cmd \| tee file \| grep X |
tee passes ALL data downstream |
tee /etc/file without sudo (root file) |
sudo tee |
permission denied silently lost |
tee f1 f2 > /dev/null then wonders why no stdout |
that’s what >/dev/null does |
drop the >/dev/null to see output |
Next → Safe Temporary Files