tee Command in Linux — Split Output to File and Terminal
The tee command reads from stdin and writes to both stdout and one or more files simultaneously — like a T-junction in a pipeline. It lets you see output on screen while saving it to a log, without running the command twice.
What You’ll Learn
You’ll capture command output to files while viewing it live, append instead of overwrite, write to multiple files at once, bypass permission restrictions with sudo, and implement logging patterns in scripts.
Why tee Matters
Every script needs both visibility and persistence — you want to see errors as they happen AND have a log for later review. tee gives you both in a single pipeline. DodaZIP uses tee to log compression jobs while showing progress on the terminal. Durga Antivirus Pro captures scan results to timestamped log files while displaying real-time alerts.
Learning Path
flowchart LR
A[Essential Commands] --> B[Redirection & Pipes]
B --> C[tee Command<br/>You are here]
C --> D[Script Logging Patterns]
C --> E[Bash Scripting]
style C fill:#f90,color:#fff
>, >>, \|). Familiarity with Bash scripting helps.Syntax Overview
command | tee [options] file(s)Options Table
| Option | Description |
|---|---|
-a | Append to file instead of overwriting |
-i | Ignore interrupt signals (Ctrl+C) |
Note: Some GNU coreutils versions also support -p (diagnose writing to pipes) but this is non-standard. Always check man tee on your system.
Examples
Example 1: Basic tee
$ echo "Backup started at $(date)" | tee backup.log
Backup started at Sat Jun 20 10:00:00 UTC 2026
$ cat backup.log
Backup started at Sat Jun 20 10:00:00 UTC 2026Output appears on screen AND is written to backup.log.
Example 2: Append Mode (-a)
$ echo "Step 1: Complete" | tee -a build.log
Step 1: Complete
$ echo "Step 2: Complete" | tee -a build.log
Step 2: Complete
$ cat build.log
Step 1: Complete
Step 2: CompleteWithout -a, the second echo would overwrite the first log entry.
Example 3: tee to Multiple Files
$ echo "ALERT: Disk usage above 90%" | tee alert.log /var/log/system-alert.log
ALERT: Disk usage above 90%
$ cat alert.log
ALERT: Disk usage above 90%
$ cat /var/log/system-alert.log
ALERT: Disk usage above 90%Write to any number of files simultaneously — useful for centralized and per-service logs.
Example 4: tee with sudo
$ echo "server-priority=high" | sudo tee -a /etc/myapp.conf
server-priority=high
$ cat /etc/myapp.conf
server-priority=highsudo tee /etc/protected-file works when sudo echo > file fails because the shell (not echo) does the redirection.
Example 5: tee in Pipelines
$ ps aux | tee processes.txt | grep "nginx"
root 1234 0.0 0.1 123456 7890 ? Ss Jun19 0:00 nginx: master
www-data 1235 0.0 0.2 123456 12345 ? S Jun19 0:01 nginx: workerThe full process list is saved to processes.txt, while only nginx lines pass through the pipe to grep.
Example 6: Variable Capture with tee
$ OUTPUT=$(ls -la | tee /dev/null)
$ echo "$OUTPUT"
total 24
drwxr-xr-x 2 user user 4096 Jun 20 10:00 .
drwxr-xr-x 3 user user 4096 Jun 20 10:00 ..
-rw-r--r-- 1 user user 45 Jun 20 10:00 file.txtBy redirecting to /dev/null, tee writes nothing to a file but still passes data through for variable capture — though in practice you’d just use OUTPUT=$(ls -la).
Example 7: Log File Creation in Scripts
#!/bin/bash
LOGFILE="/var/log/deploy-$(date +%Y%m%d-%H%M%S).log"
{
echo "Deployment started at $(date)"
systemctl restart myapp
echo "Service restarted"
systemctl status myapp
} | tee "$LOGFILE"The entire block output is captured to a timestamped log AND displayed on screen.
Example 8: Hide stdout (With /dev/null)
# Quiet tee — save to file, don't show on terminal
$ echo "Silent log entry" | tee silent.log > /dev/null
$ cat silent.log
Silent log entryRedirect tee’s stdout to /dev/null to log without terminal output.
Example 9: Capture stderr with tee
$ command_that_fails 2>&1 | tee error.log
Error: Something went wrong
Traceback (most recent call last):
File "script.py", line 42, in <module>
raise ValueError("invalid input")
ValueError: invalid input2>&1 redirects stderr to stdout, so tee captures both. This is critical for debugging scripts in production.
Example 10: Full Logging Pattern
#!/bin/bash
LOG="/var/log/backup-$(date +%Y%m%d).log"
{
echo "=== Backup Started: $(date) ==="
rsync -avz /data /backup/
echo "Exit code: $?"
echo "=== Backup Finished: $(date) ==="
} | tee -a "$LOG"A complete backup logging pattern — timestamped, append mode, captures rsync output and exit code.
Common Use Cases
| Use Case | Command |
|---|---|
| Save pipe output to log | command | tee output.log |
| Write to protected file | echo "config" | sudo tee /etc/app.conf |
| Log script output | { commands; } | tee -a script.log |
| Split to multiple logs | command | tee log1.log log2.log |
| Live monitoring + grep | tail -f log | tee captured.log | grep ERROR |
Common Errors
- Permission denied when tee-ing to /etc/: The shell can’t create the file in a protected directory. Solution:
echo "data" | sudo tee /etc/file - tee -a not used: Second call to tee overwrites the file. Always use
-awhen appending in loops. - tee doesn’t capture stderr: By default tee only sees stdout. Use
2>&1 | teeto merge stderr. - tee slows pipelines: Each write to disk adds overhead. For high-throughput logging, consider
loggerorsysloginstead. - Binary data through tee: tee works on binary streams but may corrupt terminal output. Redirect terminal to
/dev/nullfor binary pipelines.
Practice Exercises
- Basic tee: Run
ls -laand save the output to a file while viewing it. - Append: Create a script that logs each step with a timestamp to a build log.
- Sudo redirection: Add a line to
/etc/hostsusing tee. - Multiple files: Pipe a command to three different log files simultaneously.
- Full pipeline: Run a backup command, log everything, filter errors through a separate grep.
Challenge
Write a deployment script that:
- Runs
git pull,npm install, andsystemctl restart myapp - Logs ALL output (stdout + stderr) to a timestamped file
- Displays output on screen in real-time
- Emails the log if any command fails
This is the same pattern Durga Antivirus Pro uses for its update scripts.
#!/bin/bash
LOG="/var/log/deploy-$(date +%Y%m%d-%H%M%S).log"
FAILED=false
{
echo "=== Deploy started: $(date) ==="
git pull || { echo "FAIL: git pull"; FAILED=true; }
npm install || { echo "FAIL: npm install"; FAILED=true; }
systemctl restart myapp || { echo "FAIL: restart"; FAILED=true; }
echo "=== Deploy finished: $(date) ==="
} 2>&1 | tee "$LOG"
$FAILED && mail -s "Deploy FAILED" admin@example.com < "$LOG"Real-World Task
You’re running a long data migration on a production database. You want to:
- See progress on screen
- Save full output to a log file
- Filter for ERROR lines in real-time for immediate alerts
- Archive the log after completion
Build the pipeline using tee.
Solution: migration-script 2>&1 | tee migration.log | grep --line-buffered ERROR | tee errors.log
What is tee?
The tee command splits standard input to write to both one or more files and standard output simultaneously — named after the T-junction shape in plumbing.
Related Tutorials
- Essential Linux Commands — redirection patterns
- Bash Scripting Guide — advanced logging in scripts
- Linux Administration Basics — foundational administration
- sort and uniq — combine with tee for log analysis
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro