Skip to content
tee Command in Linux — Split Output to File and Terminal

tee Command in Linux — Split Output to File and Terminal

DodaTech Updated Jun 20, 2026 6 min read

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
  
Prerequisites: Understanding of essential Linux commands and shell redirection (>, >>, \|). Familiarity with Bash scripting helps.

Syntax Overview

command | tee [options] file(s)

Options Table

OptionDescription
-aAppend to file instead of overwriting
-iIgnore 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 2026

Output 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: Complete

Without -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=high

sudo 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: worker

The 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.txt

By 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 entry

Redirect 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 input

2>&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 CaseCommand
Save pipe output to logcommand | tee output.log
Write to protected fileecho "config" | sudo tee /etc/app.conf
Log script output{ commands; } | tee -a script.log
Split to multiple logscommand | tee log1.log log2.log
Live monitoring + greptail -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 -a when appending in loops.
  • tee doesn’t capture stderr: By default tee only sees stdout. Use 2>&1 | tee to merge stderr.
  • tee slows pipelines: Each write to disk adds overhead. For high-throughput logging, consider logger or syslog instead.
  • Binary data through tee: tee works on binary streams but may corrupt terminal output. Redirect terminal to /dev/null for binary pipelines.

Practice Exercises

  1. Basic tee: Run ls -la and save the output to a file while viewing it.
  2. Append: Create a script that logs each step with a timestamp to a build log.
  3. Sudo redirection: Add a line to /etc/hosts using tee.
  4. Multiple files: Pipe a command to three different log files simultaneously.
  5. 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, and systemctl 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:

  1. See progress on screen
  2. Save full output to a log file
  3. Filter for ERROR lines in real-time for immediate alerts
  4. 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

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro