The Linux tee command is a very simple yet useful utility. Having only two options for a command is unusual, but sometimes less is more. The tee command serves is purpose beautifully.

In this article we will cover the basic usage, but also some more interesting and advanced use cases for tee.

As the name tee implies, it works like a tee connector in plumbing by taking input and routing output to two different locations (stdout and a file).

tee command diagram

If you are new to Linux, I recommend you read our "Introduction to Linux IO, Standard Streams, and Redirection". This will give you a good understanding of stdin, stdout and stderr (standard streams) and pipe. We will use those terms often in this article.

Basic tee Command Usage

In the most basic usage, you would just pipe a command to tee and give it a filename. This will allow the command to still print to the screen, but also save the results in a file.

In this example, we will look for users who are using the bash shell in /etc/passwd and print the output to both stdout and a file named output.txt.

$ grep bash /etc/passwd | tee output.txt
root:x:0:0:root:/root:/bin/bash
savona:x:1000:1000:savona:/home/savona:/bin/bash

$ cat output.txt
root:x:0:0:root:/root:/bin/bash
savona:x:1000:1000:savona:/home/savona:/bin/bash

As you can see, the output.txt file contains the same text that was sent to standard output.

Sending Output to Multiple Files

You can send the data from the tee command to multiple files at once. To do this you simple add a list of files after the tee command.

grep bash /etc/passwd | tee output.txt file2.txt file3.txt

The multiple output files will have identical contents.

Appending to a File with the tee Command

One of the two options available to the tee command is append ( -a ). This will allow you to append additional lines to an existing file. The default behavior would be to overwrite the contents of the file.

$ grep halt /etc/passwd | tee -a output.txt 
halt:x:7:0:halt:/sbin:/sbin/halt

$ cat output.txt
root:x:0:0:root:/root:/bin/bash
savona:x:1000:1000:savona:/home/savona:/bin/bash
halt:x:7:0:halt:/sbin:/sbin/halt

The output was displayed on the screen and appended to the output.txt file.

More Advanced tee Command Usage

There are a lot of cool things you can do with tee. Here we will show you some more advanced uses for the tee command.

Pipe Data to Multiple Commands

If you read our "Introduction to Linux IO, Standard Streams, and Redirection" you should be familiar with piping data to another command. With a normal pipe you only have one output. You can use the tee command to split that pipe into multiple outputs. But not to just files, you can also split the output to different commands or processes.

In this example we take the output of the grep command and pipe it to two different commands (tr and cut).

$ grep -oh -m1 garage access.log | tee >(tr [:lower:] [:upper:]) >(cut -c1)
garage
GARAGE
g

The output shows the original output from the grep command (like using basic tee), then shows the output manipulated by the tr command. In addition it also shows the same output ran through the cut command.

Tail a Log File and Write The Output to a File at the Same Time

Often you want to watch a log file scroll (tail) and use grep to only show what you need. If you want that output saved to a file you would have to redirect it like so:

tail -f access.log | grep garage > output.txt

This is great, but you can't see what is being written to output.txt unless you open an additional terminal and then tail the output.txt file. Enter the tee command to save the day.

$ tail -f access.log | egrep --line-buffered "garage" | tee garage-output.txt
191.182.199.16 - - [12/Dec/2015:19:02:40 +0100] "GET /images/stories/raith/garage.jpg HTTP/1.1" 200 57339 "http://almhuette-raith.at/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36" "-"
188.45.108.168 - - [12/Dec/2015:19:44:08 +0100] "GET /images/stories/raith/garage.jpg HTTP/1.1" 200 57339 "http://www.almhuette-raith.at/" "Mozilla/5.0 (Linux; Android 4.4.2; de-at; SAMSUNG GT-I9301I Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36" "-
"
...OUTPUT TRUNCATED...


$ cat garage-output.txt
191.182.199.16 - - [12/Dec/2015:19:02:40 +0100] "GET /images/stories/raith/garage.jpg HTTP/1.1" 200 57339 "http://almhuette-raith.at/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36" "-"
188.45.108.168 - - [12/Dec/2015:19:44:08 +0100] "GET /images/stories/raith/garage.jpg HTTP/1.1" 200 57339 "http://www.almhuette-raith.at/" "Mozilla/5.0 (Linux; Android 4.4.2; de-at; SAMSUNG GT-I9301I Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36" "-"
...OUTPUT TRUNCATED...

Using tee, you can view the output and write it out to a file at the same time.

Capture Data Before it is Changed

One interesting way to use the tee command is to capture a data stream before you edit it.

For example, let's say you were looking through a large apache access.log file. You want to pull all of the HTTP POST requests, save them to a file, then do additional manipulation to the output.

$ grep POST access.log | tee post_requests.txt | cut -d" " -f1 | head -5
109.169.248.247
46.72.177.4
83.167.113.100
95.29.198.15
109.184.11.34

$ cat post_requests.txt
109.169.248.247 - - [12/Dec/2015:18:25:11 +0100] "POST /administrator/index.php HTTP/1.1" 200 4494 "http://almhuette-raith.at/administrator/" "Mozilla/5.0 (Windows NT 6.0; rv:34.0) Gecko/20100101 Firefox/34.0" "-"
46.72.177.4 - - [12/Dec/2015:18:31:08 +0100] "POST /administrator/index.php HTTP/1.1" 200 4494 "http://almhuette-raith.at/administrator/" "Mozilla/5.0 (Windows NT 6.0; rv:34.0) Gecko/20100101 Firefox/34.0" "-"
83.167.113.100 - - [12/Dec/2015:18:31:25 +0100] "POST /administrator/index.php HTTP/1.1" 200 4494 "http://almhuette-raith.at/administrator/" "Mozilla/5.0 (Windows NT 6.0; rv:34.0) Gecko/20100101 Firefox/34.0" "-"
95.29.198.15 - - [12/Dec/2015:18:32:11 +0100] "POST /administrator/index.php HTTP/1.1" 200 4494 "http://almhuette-raith.at/administrator/" "Mozilla/5.0 (Windows NT 6.0; rv:34.0) Gecko/20100101 Firefox/34.0" "-"
109.184.11.34 - - [12/Dec/2015:18:32:56 +0100] "POST /administrator/index.php HTTP/1.1" 200 4494 "http://almhuette-raith.at/administrator/" "Mozilla/5.0 (Windows NT 6.0; rv:34.0) Gecko/20100101 Firefox/34.0" "-"
...OUTPUT TRUNCATED...

The tee command allowed us to save the output of the original command, but kept the output flowing through the pipe to be further manipulated by the cut and head commands.

Ignoring Interrupts

The last option is ignore interrupts ( -i ). This tells tee to continue and exit gracefully if the user sends an interrupt (CTRL+C). There is no easy way to demonstrate this in a blog post, so here is an example of the syntax.

$ grep GET access.log | tee -i log-get.txt

I have never found a need for this, but I am guessing if you had a really long running command being piped to tee, it might be useful.

Conclusion

Other command may give tee option envy, but I have a lot of love for this pragmatic little utility. I don't need fancy options or long drawn out man pages, I just need to get the job done.

In all seriousness, the tee command has proven to be a staple in my day to day operations. I use it constantly and am surprised when we get junior admins or interns on board and they don't know about it. It does one thing, but does it well.

Over the years I have seen the tee command used in some creative ways. I would love it if you would share any interesting uses of the tee command in the comments.

Resources