Right, so you’ve got a command that’s about to spew a glorious, information-dense stream of data to your terminal (stdout). You want to capture that stream to a file for posterity (or debugging, or your boss), but you also want to watch it scroll by in real-time to make sure it’s not doing something catastrophically stupid.

Your first thought might be to just redirect the output: my_command > output.log. Great. Now you have a file. And you also have the profound silence of a command that ran and told you absolutely nothing. You’re flying blind. Did it work? Is it hung? Who knows! You’re not a psychic.

This is the exact problem tee was invented to solve. The name is a perfect metaphor; it’s a plumbing T-junction for your data stream. It takes the input from stdin, writes a copy of it to one or more files you specify, and then dutifully passes the original stream along to stdout so you can still see it. It’s the ultimate “why not both?” of the command line.

The Absolute Basics

The syntax is dead simple. You pipe (|) your command’s output into tee and tell it what file to create.

# Let's say you're grepping through a massive source code repository
grep -r "function_that_breaks_everything" /path/to/src/ | tee grep_results.txt

As this runs, every matching line will print to your terminal and be written to grep_results.txt. You get your real-time feedback and a perfect log file, simultaneously. No more waiting for a long command to finish before you can check its output.

Why You’d Actually Use This

Think beyond just logging. This is incredibly powerful for debugging multi-stage pipes. Let’s say you’ve crafted a beautiful, 15-part pipeline to parse your server logs. It’s not working. Which part is failing? tee lets you tap into the stream at any point to see what the data looks like.

# Let's debug this monster
cat server.log | grep "ERROR" | awk '{print $5}' | sort | uniq -c | sort -nr

# Stick a tee in the middle after the awk command to see what it's outputting
cat server.log | grep "ERROR" | awk '{print $5}' | tee awk_output.txt | sort | uniq -c | sort -nr

Now you can check awk_output.txt to see if the awk command is even producing the field you think it is. It’s like a diagnostic port for your data pipeline.

Appending, Not Clobbering

By default, tee will overwrite the target file, just like a > redirect. If you’d rather add to the file (like a >> redirect), use the -a (append) flag. This is crucial for creating ongoing logs from multiple commands.

# First run
command_that_takes_forever --stage1 | tee -a master_build_log.txt

# Later, another run appends to the same log
command_that_takes_forever --stage2 | tee -a master_build_log.txt

Teeing to Multiple Files and stdout

Here’s a neat trick: you can specify multiple files, and tee will write the output to all of them. You want a detailed log for you and a summary log for a report? No problem.

generate_report.sh | tee detailed_debug_log.txt summary_for_boss.txt

Both files will contain the exact same output from generate_report.sh, which is also still printing to your screen. It’s one stream, many destinations.

The Crucial Pitfall: It Doesn’t Tee stderr

Pay very close attention here, because this trips everyone up. tee only operates on stdout. Any output your command sends to stderr (error messages, warnings, progress bars) will completely bypass tee and go straight to your terminal. It will not be captured in your file.

If you want to capture everything—the good (stdout) and the bad (stderr)—you need to redirect stderr into stdout first, then pipe that combined stream into tee.

# The wrong way - errors will show on screen but not be in the file
my_script.sh | tee output.log

# The right way to capture EVERYTHING
my_script.sh 2>&1 | tee output.log

Let’s break that down: 2>&1 means “take whatever is on file descriptor 2 (stderr) and redirect it to wherever file descriptor 1 (stdout) is currently going.” Which is right into our tee pipe. Now your log file output.log will be a complete record of the entire session, errors and all, while you still watch it all happen live. It’s the best of all worlds. Use this pattern constantly. You’re welcome.