What it is
tee is a command-line utility that reads from standard input and writes to standard output and one or more files simultaneously, allowing you to split a data stream.
Installation
Linux:
tee is typically pre-installed on most Linux distributions. If not, you can install it via your package manager:
sudo apt update && sudo apt install coreutils # Debian/Ubuntu
sudo yum install coreutils # CentOS/RHEL
sudo dnf install coreutils # Fedora
macOS:
tee is pre-installed on macOS.
Windows:
tee is not a built-in Windows command. You can obtain it through:
- Git Bash: If you have Git for Windows installed,
teeis available within the Git Bash terminal. - Cygwin: Install the
coreutilspackage via the Cygwin setup. - WSL (Windows Subsystem for Linux): Install a Linux distribution (e.g., Ubuntu) from the Microsoft Store, and
teewill be available within that Linux environment.
Commands / Usage
Basic Usage
Writing to a file and stdout:
echo "Hello, world!" | tee output.txt
echo "Hello, world!": Sends the string "Hello, world!" to standard output.|: Pipes the standard output ofechoto the standard input oftee.tee output.txt: Reads from standard input, writes tooutput.txt, and also writes to standard output.
Output:
Hello, world!
And output.txt will contain:
Hello, world!
Appending to Files
Appending to a file and stdout:
echo "Another line." | tee -a log.txt
-a: Appends the output to the specified file(s) instead of overwriting them.
Writing to Multiple Files
Writing to stdout and two files:
ls -l | tee file_list.txt file_list.bak
ls -l: Lists directory contents in long format to standard output.tee file_list.txt file_list.bak: Writes the output ofls -lto bothfile_list.txtandfile_list.bak, and also prints it to standard output.
Ignoring Interrupts
Ignoring SIGINT (Ctrl+C):
yes | tee -i output.log
-i: Preventsteefrom being killed by SIGINT (usually Ctrl+C). This is useful when you want to ensure output is written even if the upstream command is interrupted.yes: Continuously outputs "y" followed by a newline to standard output.
Common Patterns
Logging command output and errors to a file:
ls /path/to/nonexistent_dir 2>&1 | tee command_log.txt
ls /path/to/nonexistent_dir: Attempts to list a directory that likely doesn’t exist, producing an error on stderr.2>&1: Redirects standard error (file descriptor 2) to standard output (file descriptor 1).| tee command_log.txt: Writes both the standard output (which now includes errors) tocommand_log.txtand displays it on the terminal.
Saving output of a long-running process:
./my_long_script.sh | tee script_output.log
- Runs
my_long_script.sh, saves its output toscript_output.log, and displays it on the terminal simultaneously.
Creating a backup while processing:
cat data.csv | tee data.csv.bak | grep "important" > filtered_data.csv
cat data.csv: Reads the content ofdata.csv.tee data.csv.bak: Writes the content todata.csv.bak(creating a backup) and passes it through to standard output.grep "important": Filters the stream for lines containing "important".> filtered_data.csv: Redirects the filtered output tofiltered_data.csv.
Using tee with process substitution (Bash/Zsh):
diff file1.txt file2.txt > >(tee diff_output.txt)
>(...): Process substitution.teeruns in a subshell, and its standard output is treated as a file thatdiffcan write to. This is less common than standard piping but can be useful in specific scenarios. In this case,diff’s output is sent toteewhich writes it todiff_output.txtand stdout.
Gotchas
- Overwriting vs. Appending: By default,
teeoverwrites files. Always use-aif you intend to append. - Error Handling:
teeitself generally won’t report errors if it can’t write to a file (e.g., due to permissions). The error will usually come from the shell attempting the redirection, or the upstream command might fail. - Buffering: Standard output buffering can sometimes affect how quickly you see output on the terminal versus when it appears in the file, especially with very fast-producing commands. Using
stdbufor ensuring commands flush their buffers can help. tee -iand Interrupts: While-ipreventsteefrom being killed by SIGINT, the upstream command might still be killed if it receives the signal. If you want to ensure the entire process (upstream andtee) continues, you might need more complex signal handling ornohup.