Side output in Bash allows you to display information on the terminal while simultaneously saving it to a file. The most common and robust method is using the tee command, which reads from standard input and writes to both standard output and one or more files. This technique is essential for logging script progress without losing real-time feedback.
Understanding Standard Streams
To master side output, you must first understand how Bash handles data streams. Every process has three default streams: standard input (stdin), standard output (stdout), and standard error (stderr). By default, stdout points to your terminal screen, allowing you to see command results as they happen. Redirect operators like > change where stdout goes, typically sending it to a file instead of the screen. However, this redirection hides the output from the user entirely, which is often undesirable during long-running scripts.
Side output solves this by splitting the stream. It ensures that the data remains visible on the console while also being captured for later review. This dual behavior is particularly useful when debugging complex pipelines or monitoring system processes that run for extended periods. Without this capability, you would either have to sacrifice visibility for persistence or vice versa.
The Primary Solution: Using tee
The tee utility is the standard tool for achieving side output. It takes input from stdin and copies it to both stdout and the specified file(s). The name comes from the T-splitter used in plumbing, which accurately describes its function in data processing. When you pipe a command into tee, the output continues down the pipeline to any subsequent commands, while a copy is written to disk.
command | tee output.log
In this example, command runs normally, and its output appears on your screen. Simultaneously, output.log is created (or overwritten) with the exact same content. If you want to append to an existing log file rather than overwriting it, use the -a flag:
command | tee -a output.log
This approach is non-destructive and safe for iterative testing. It works seamlessly with other Unix utilities, maintaining the flow of data through pipes. You can chain multiple tee commands if you need to save logs to different locations based on specific criteria, though simple piping is usually sufficient for most scripting needs.
Capturing Both Standard Output and Error
Often, scripts produce errors alongside normal messages. By default, stderr bypasses the pipe and goes directly to the terminal. To capture both streams in your side output file, you must redirect stderr to stdout before piping to tee. This is done using the 2>&1 syntax, which tells Bash to send file descriptor 2 (error) to file descriptor 1 (standard output).
command 2>&1 | tee output.log
This ensures that warning messages and stack traces are included in your log file while still appearing on the screen. It provides a complete picture of the script's execution environment. Without this step, your log file might appear empty or incomplete even if the script failed critically.
Alternative Methods and Considerations
While tee is the most elegant solution, other methods exist depending on your specific constraints. One alternative is using process substitution or subshells to redirect output manually. For instance, you can wrap a command group in parentheses and redirect the entire block’s output to a file while letting individual commands print to the terminal. However, this approach is less flexible for streaming data and harder to maintain in complex scripts.
Another consideration is performance. For very large outputs, tee introduces minimal overhead because it simply duplicates the byte stream. However, if you are dealing with massive binary files, ensure your filesystem has enough space and I/O bandwidth. As noted in previous troubleshooting guides, issues often arise from quoting problems or unexpected file permissions rather than the tee mechanism itself.
For scenarios where you only need to log and not interact with the output further, direct redirection with >> inside the script loop is an option. But this requires manual management of file descriptors and breaks the clean separation of concerns that tee provides. It is generally recommended to stick to tee for clarity and reliability.
Practical Examples in Scripting
Consider a backup script that compresses a directory. You want to see which files are being processed in real-time but also keep a record of what was backed up. Using tar with tee achieves this perfectly:
tar czf backup.tar.gz /data 2>&1 | tee backup.log
This single line handles compression, logging, and visibility. If the backup fails, the log file contains the error message, aiding in diagnosis. This pattern is reusable across many administrative tasks, from deploying code to running database migrations.
Additionally, you can combine side output with conditional logic. If you only want to log errors, you can filter the output using grep before passing it to tee. This keeps your logs clean and focused on actionable items. Remember that filtering happens after the command runs, so the original command still sees all output unless you modify its internal behavior.
For more advanced date handling in these logs, consider integrating time stamps. Tools like those discussed in our guide on date-time conversion can help format log entries consistently. Proper formatting makes it easier to parse logs later using automated tools or grep searches.
Troubleshooting Common Issues
If your side output does not appear as expected, check two main areas: permissions and buffering. Ensure the user running the script has write access to the target file. Also, note that some commands buffer their output when not connected to a terminal. In such cases, tee might delay displaying output until the buffer fills. Using --line-buffered with tee can force immediate display of each line, improving interactivity.
Another common pitfall is misunderstanding how pipes work. If you place tee incorrectly in the pipeline, you might lose data or create circular dependencies. Always verify the direction of data flow. Referencing resources on preventing script interruptions can also help if your logs seem to stop abruptly due to signal handling.
Frequently Asked Questions
Does tee corrupt binary data?
No, tee handles binary data correctly and does not corrupt null bytes or special characters. It operates at the byte level, making it safe for copying executables, images, or compressed archives just as it is for text logs.
How do I timestamp every line in my log?
You can pipe the output of tee into a loop or use ts from the moreutils package. Alternatively, wrap your command to prepend timestamps manually, ensuring each line gets a unique identifier for chronological sorting.
Can I split output to multiple files at once?
Yes, tee accepts multiple filenames. For example, command | tee file1.txt file2.txt will write identical content to both files while still displaying it on the screen. This is useful for separating logs by severity or component.
Why is my output delayed when using tee?
This is usually due to output buffering. Commands often buffer output when writing to pipes instead of terminals. Adding --line-buffered to your tee command forces it to flush after every newline, providing real-time updates.
Is there a way to log only errors?
You can filter the stream using grep before tee, such as command 2>&1 | grep -i error | tee error.log. This ensures only lines containing "error" are saved and displayed, keeping other logs separate.