Linux Basic — Writing with tee

4 minute read

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:

  1. See output while logging it
  2. Write to a file that needs sudo from inside a pipeline
  3. 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 to issues.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

Updated: