ZetCode

Linux tee Command

last modified October 23, 2026

The tee command in Linux reads from standard input and writes it both to standard output and to one or more files. It is named after the T-splitter used in plumbing: the data flows through the pipeline while a copy is saved on the side.

tee is commonly used for logging the output of commands while watching it on the terminal, saving intermediate results of a pipeline, and writing to files that require root privileges.

Basic usage

In the simplest case, tee copies its input to a file and to the terminal.

echo "Hello, World!" | tee output.txt

The message is printed on the terminal and written to output.txt. If the file exists, it is overwritten.

$ echo "Hello, World!" | tee output.txt
Hello, World!
$ cat output.txt
Hello, World!

Appending to a file

The -a option appends to the given files instead of overwriting them.

echo "Second line" | tee -a output.txt

The new line is added to the end of output.txt, which now contains two lines.

$ echo "Second line" | tee -a output.txt
Second line
$ cat output.txt
Hello, World!
Second line

Writing to multiple files

tee accepts any number of file names.

date -d "2026-10-23 10:00" "+%F %T" | tee log1.txt log2.txt

The formatted date is written to both log1.txt and log2.txt and also printed on the terminal.

$ date -d "2026-10-23 10:00" "+%F %T" | tee log1.txt log2.txt
2026-10-23 10:00:00
$ cat log1.txt log2.txt
2026-10-23 10:00:00
2026-10-23 10:00:00

Saving an intermediate result

Placed in the middle of a pipeline, tee saves the data at that point while passing it on to the next command.

sort words.txt | tee sorted.txt | wc -l

The sorted words are stored in sorted.txt, and wc -l receives the same data and counts the lines.

$ sort words.txt | tee sorted.txt | wc -l
5
$ cat sorted.txt
bear
falcon
forest
rock
sky

Suppressing the terminal output

When only the file copy is needed, standard output can be discarded.

echo "quiet" | tee quiet.txt > /dev/null

Redirecting to /dev/null hides the output on the terminal. The data is still written to quiet.txt.

$ echo "quiet" | tee quiet.txt > /dev/null
$ cat quiet.txt
quiet

Logging standard error

A pipe passes only standard output. To capture error messages and warnings, standard error must be redirected into the pipe as well.

build.sh
#!/bin/bash

echo "compiling sources"
echo "warning: unused variable 'x'" >&2
echo "linking binary"
echo "build finished"

The script prints a warning to standard error. In the first run, the warning appears on the terminal but is missing from build.log. The 2>&1 redirection merges standard error into standard output, so the second log is complete. Bash also offers the shorthand |& for 2>&1 |.

$ ./build.sh | tee build.log
compiling sources
warning: unused variable 'x'
linking binary
build finished
$ cat build.log
compiling sources
linking binary
build finished
$ ./build.sh 2>&1 | tee build.log
compiling sources
warning: unused variable 'x'
linking binary
build finished
$ cat build.log
compiling sources
warning: unused variable 'x'
linking binary
build finished

Writing to protected files with sudo

Output redirection is performed by the shell that runs as the current user, so sudo does not help with > or >>. Piping to sudo tee solves this.

echo "127.0.0.1 dev.local" | sudo tee -a /etc/hosts

In the first command, sudo elevates only echo; the redirection to /etc/hosts fails. In the second one, tee itself runs as root and appends the line to the file. Add > /dev/null to avoid echoing the line back.

$ sudo echo "127.0.0.1 dev.local" >> /etc/hosts
bash: /etc/hosts: Permission denied
$ echo "127.0.0.1 dev.local" | sudo tee -a /etc/hosts
127.0.0.1 dev.local
$ tail -n 1 /etc/hosts
127.0.0.1 dev.local

Splitting a stream with process substitution

Combined with Bash process substitution, tee can feed the same data to several commands at once.

seq 1000 | tee >(gzip > numbers.gz) >(sha256sum > numbers.sha256) | tail -n 1

The >(...) constructs look like files to tee, but they are pipes to other commands. The numbers are compressed and checksummed in one pass, while tail prints the last line. Decompressing the archive yields the same checksum.

$ seq 1000 | tee >(gzip > numbers.gz) >(sha256sum > numbers.sha256) | tail -n 1
1000
$ zcat numbers.gz | sha256sum
67d4ff71d43921d5739f387da09746f405e425b07d727e4c69d029461d1f051f  -
$ cat numbers.sha256
67d4ff71d43921d5739f387da09746f405e425b07d727e4c69d029461d1f051f  -

Exit status in pipelines

The exit status of a pipeline is that of its last command, which hides failures of the commands before tee.

false | tee result.txt; echo "status: $?"
set -o pipefail
false | tee result.txt; echo "status: $?"

Although false fails, the pipeline returns 0 because tee succeeded. With the pipefail option, the pipeline returns the status of the last failing command. Use it in scripts that log through tee; the PIPESTATUS array holds the status of each command as well.

$ false | tee result.txt; echo "status: $?"
status: 0
$ set -o pipefail
$ false | tee result.txt; echo "status: $?"
status: 1

Best practices for tee

Source

GNU tee manual

In this article, we have used the tee command to copy data to files and the terminal, append to logs, capture errors, write protected files, and split a stream between several commands.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Linux tutorials.