Use tee to Save a Pipeline and Keep Its Output Visible

A command that prints its result once is no use when you also need a copy on disk, and tee is the fix: it splits one stream into both. You will use GNU tee to copy standard input to the terminal and one or more files at the same time. The installed command is GNU coreutils 9.4, and the local manual page is dated August 2026.

Allow about ten minutes. You need a shell and a writable working directory. These examples read and write ordinary files, so they do not need sudo. Elevated privileges are only relevant when the destination directory itself is protected, and using them can hide an ownership or path mistake.

1. Copy input to a file and standard output

Start with a small, disposable input file:

$ printf '%s\n' 'first line' 'second line' > input.txt
$ tee saved.txt < input.txt
first line
second line

tee reads from standard input. It writes the same bytes to each named file and to its own standard output. The terminal output above is therefore the copy that the next command in a pipeline, or your terminal, can receive. The named file is replaced if it already exists.

Checkpoint: compare the two text files:

$ cmp --silent input.txt saved.txt && printf '%s\n' 'saved.txt matches input.txt'
saved.txt matches input.txt

If cmp prints nothing and returns a non-zero status, stop and inspect the paths. Do not assume that a command succeeded merely because some text appeared on screen.

2. Put tee in a pipeline

The usual form is a command, a pipe, tee, and a later command. This example displays a directory listing while saving that listing:

$ printf '%s\n' report.txt notes.txt archive.tar > names.txt
$ sort names.txt | tee sorted-names.txt | sed -n '1,2p'
archive.tar
names.txt

The first output copy goes to sorted-names.txt. The second goes through the final pipe to sed, which prints only its first two lines. A file named tee is not involved: the shell finds the program through PATH.

For a real command, replace the first stage with a command that produces standard output:

$ journalctl --no-pager -u example.service | tee service-check.txt

This reads the service journal and changes no service state. Whether it can read the journal depends on the host's journal permissions. Use sudo only if the command reports a permission failure and your local policy permits that access. Do not add sudo to a pipeline automatically: it usually affects only the command immediately following it.

3. Append instead of replacing a file

Use -a or --append when the existing file is a log that must be retained:

$ printf '%s\n' 'run 1' | tee -a runs.log
run 1
$ printf '%s\n' 'run 2' | tee --append runs.log
run 2
$ cat runs.log
run 1
run 2

Without -a, the first write opens each destination for replacement. With it, each destination is opened for appending. Neither option creates a backup, and an append is not a transaction: a process or machine failure can still leave a partial final line.

Checkpoint: inspect the result before using it as an audit record:

$ wc -l runs.log
2 runs.log

Choose a separate file for each run if you need clean, independently verifiable records. Do not append secrets or credentials to a log merely because tee makes the output convenient to capture.

4. Avoid accidental truncation

Shell redirection happens before the command starts. This command truncates saved.txt immediately, even if the producer later fails:

$ some-command | tee saved.txt

That is correct when replacement is intended, but it is a common distraction trap during an investigation. Before replacing a useful file, use a new destination or make a deliberate backup:

$ cp --preserve=all saved.txt saved.txt.bak
$ producer-command | tee saved.txt.new
$ test "${PIPESTATUS[0]}" -eq 0 && mv saved.txt.new saved.txt

PIPESTATUS is a Bash array containing the status of each pipeline component. The test above moves the temporary output into place only when the producer succeeded. If it fails, leave the original file in place and remove the incomplete temporary file after checking its exact path:

$ rm saved.txt.new
$ mv saved.txt.bak saved.txt

The rm command is destructive. Run it only when you have confirmed that saved.txt.new is the disposable file. The mv command restores the backup; do not run it if the backup is not the version you want.

5. Send input to several files

Give tee more than one file name when the same input must be copied to several destinations:

$ printf '%s\n' 'release candidate' | tee copy-a.txt copy-b.txt
release candidate
$ cmp --silent copy-a.txt copy-b.txt && printf '%s\n' 'both copies match'
both copies match

The files use replacement mode unless -a is present. A failure writing one destination can affect the command status and the pipeline's later behaviour. Check the final status when the copies matter:

$ printf '%s\n' 'check' | tee copy-a.txt copy-b.txt >/dev/null
$ printf '%s\n' "$?"
0

A zero status says that this invocation did not report a write failure. It does not prove that a human has reviewed the contents or that a later consumer accepted them.

6. Handle write errors deliberately

GNU tee 9.4 has --output-error modes for choosing how write failures are handled: warn, warn-nopipe, exit, and exit-nopipe. The local manual says that, when this option is absent, tee exits immediately on a pipe write error and diagnoses errors writing to non-pipe outputs.

The -p option selects a more appropriate mode for pipes. Its default mode is warn-nopipe. Use an explicit mode when a script needs the policy to be visible during review:

$ producer-command | tee --output-error=exit-nopipe capture.txt | consumer-command

Here, errors writing to non-pipe outputs cause tee to exit, while broken pipes are treated according to the mode's documented nopipe rule. Test error handling with a controlled temporary destination before putting it into monitoring or deployment code. Do not infer a mode's result from a successful, ordinary pipeline.

7. Check the installed interface

Options can vary between implementations, so check the executable that will run on the target machine:

$ command -v tee
/usr/bin/tee
$ tee --version | sed -n '1p'
tee (GNU coreutils) 9.4
$ tee --help | sed -n '1,12p'
Usage: tee [OPTION]... [FILE]...
Copy standard input to each FILE, and also to standard output.

The short options in this guide are -a, -i, and -p. -i makes tee ignore interrupt signals; it is not a general fix for a producer that is stuck or a consumer that has stopped reading. --help and --version do not read or modify your input files.

Done means