The pipe (|) and tee commands in Linux and Unix (macOS)

Sunday, July 14, 2024 at 5:21 PM | 4 min read

Last modified on Thursday, August 6, 2026 at 10:27 PM

, , , , , , , ,

Close-up of a hand placing a golf ball on a tee in sunlight, outdoors.

Photo by Kampus Production on pexels.com

Table of Contents

In this article, I discuss the pipe (|) command, aka unnamed pipe, which transmits output of one command as input to another command. It redirects the stdout of one process to the stdin of another for further processing.

What is the tee command?

The tee command reads the stdin and writes it to both stdout and one or more files. According to Wikipedia,

[tee] derives from the tee pipe fitting even though the tee command duplicates the input into each output instead of dividing the input into portions for each output.

Explaining the tee command by example

The best way to describe what tee does is by example. Let's say I ran the following in Terminal from inside a folder called text-files:

ls | tee tee.txt

It would return the following:

duplicates.txt first-named-pipe list-home-directory.txt names1.txt names2.txt tee.txt

And if I ran cat tee.txt, it would yield:

duplicates.txt first-named-pipe list-home-directory.txt names1.txt names2.txt tee.txt

The stdout of the ls command is "piped" into the tee command via | as stdin, and then is both displayed as stdout in Terminal and written in tee.txt as stdout.

And if I ran:

history | tee history.txt

Something like the following would be output in Terminal:

# history.txt ... (truncated) 372 ls | tee -a names1.txt names2.txt tee.txt 373 cat tee.txt 374 ls | tee tee.txt 375 ls 376 cat tee.txt 377 history | tee history.txt

And the same content to Terminal is saved to a newly created file called history.txt as output, thanks to the tee command.

The tee command, history and the -a flag

Let's say I ran more commands, and then wanted to append these new commands added to Terminal history to the bottom of the history.txt file. Running history | tee history.txt would erase all previous content and replace it with the new history. To make sure that the new commands are appended to the end of the file and don't overwrite its previous contents, I run the following in Terminal:

history | tee -a history.txt

This results in the following:

... 378 ls 379 cat history.txt 380 cat history.txt 381 cat history.txt 382 history | tee history.txt

And inside history.txt:

# history.txt ... 378 ls 379 cat history.txt 380 cat history.txt 381 cat history.txt 382 history | tee history.txt

The -a flag, aka --append, is analogous to the output redirect append operator (>>). It appends new data to the end of a file, thereby not overwriting what is already there.

The tee command, cat, and two file inputs

tee can write the same input to more than one file at once. Let's say I run:

cat history.txt | tee history.txt history2.txt

This command returned two empty files and nothing to Terminal. Why?

The command erased the contents of history.txt before reading it. This happened because of how the command line handles pipes and file redirection.

When I run a pipeline using the unnamed pipe, the shell opens and prepares the commands at the same time. The shell starts running cat history.txt and tee history.txt history2.txt simultaneously.

To write data to history.txt, the tee command instantly opens the file and wipes it clean to start from scratch.

If the tee command opens the file to write it right before cat finishes reading it, history.txt becomes completely empty. cat then reads an empty file, sending nothing through the pipe, and both history.txt and history2.txt become blank.

Before running the cat history.txt | tee history.txt history2.txt again, I repopulated history.txt with history > history.txt, since the previous command had wiped it out. And when I ran this command again after a bit of time and a few more commands, I added the -a or --append flag:

cat history.txt | tee -a history.txt history2.txt

Running cat again, along with the -a flag, appends history.txt's contents to its previous content, thereby doubling the number of lines in the file. The same goes for history2.txt. Its number of lines doubled as well.

The tee command and wc

wc can report the number of lines, words, characters, bytes, and maximum line length. But by default, with no flags, it returns just lines, words, and bytes, in that order. It does not necessarily find all those items at once.

When I execute:

wc -l history2.txt

it returns the following in Terminal:

804 history2.txt

And when I run:

wc -l history.txt

It produces the following in Terminal:

804 history.txt

If I run the command:

wc history.txt

it produces:

804 2094 21800 history.txt

804 represents the number of (new) lines, 2094 represents the number of words, and 21800 the number of bytes. And if I run the following in Terminal:

wc -L history.txt

It yields:

67 history.txt

67 represents the max length or "width" of a line. In other words, 67 characters (including spaces).

There are other options to choose from for wc and tee, and you can learn more about them by running man wc and man tee in Terminal.

Conclusion

In this piece, I walk through how the pipe and tee commands work together in conjunction with other commands such as cat, wc, and history to create practical instructions applicable in daily workflows. I accompany my explanations with live code snippets and their outputs.

loading