Table of Contents
- Understanding Standard I/O Streams
- Output Redirection: Controlling Where Output Goes
- Input Redirection: Feeding Data to Commands
- Redirecting Standard Error (stderr)
- Pipes (
|): Chaining Commands for Powerful Workflows - Named Pipes (FIFOs): Persistent Inter-Process Communication
- Advanced Redirection Techniques
- Practical Bash Script Examples
- Common Pitfalls and Best Practices
- Conclusion
- References
1. Understanding Standard I/O Streams
Before diving into redirection and pipes, it’s critical to understand standard input/output (I/O) streams. These are pre-defined channels that Unix-like systems use to handle input and output for processes. By default, every command you run in Bash interacts with three standard streams:
1.1 What Are Standard Streams?
| Stream Name | Purpose | Default Destination/Source |
|---|---|---|
| Standard Input (stdin) | Input data fed into the command (e.g., keyboard input, file content). | Keyboard ( /dev/stdin ) |
| Standard Output (stdout) | Normal output generated by the command (e.g., results, status messages). | Terminal ( /dev/stdout ) |
| Standard Error (stderr) | Error messages or diagnostic output (e.g., “file not found” errors). | Terminal ( /dev/stderr ) |
1.2 File Descriptors: The Numeric Identifiers
To interact with these streams programmatically, the system assigns them file descriptors—numeric IDs that represent open files (or streams). The standard streams use the following file descriptors:
0: stdin1: stdout2: stderr
These descriptors are the “handles” Bash uses to redirect input/output. For example, to redirect stderr, you’ll reference file descriptor 2.
2. Output Redirection: Controlling Where Output Goes
Output redirection lets you send stdout (or stderr) to a file, another command, or even discard it entirely—instead of printing it to the terminal.
2.1 Overwriting Files with >
The > operator redirects stdout to a file, overwriting the file if it already exists.
Syntax:
command > output_file
Example: Save the output of ls -l to a file named file_list.txt:
ls -l > file_list.txt
Now file_list.txt contains the directory listing. If file_list.txt existed before, its old content is lost!
2.2 Appending to Files with >>
To append output to a file (instead of overwriting), use >>. This is safer for logging or adding data to existing files.
Syntax:
command >> output_file
Example: Append today’s date to a log file:
date >> activity.log
Run this multiple times, and activity.log will grow with new timestamps.
2.3 Redirecting to /dev/null (Discarding Output)
Sometimes you want to suppress output (e.g., ignore “success” messages). /dev/null is a special file that discards all data written to it (often called the “bit bucket”).
Syntax:
command > /dev/null
Example: Run find but hide “permission denied” errors (we’ll cover stderr redirection later, but this suppresses stdout):
find / -name "*.log" > /dev/null
3. Input Redirection: Feeding Data to Commands
Input redirection lets you send data from a file (or inline text) to a command’s stdin, instead of typing input manually.
3.1 Reading from a File with <
The < operator redirects stdin to read from a file.
Syntax:
command < input_file
Example: Use wc -l (count lines) to count lines in file.txt by redirecting stdin from file.txt:
wc -l < file.txt
This is equivalent to wc -l file.txt, but explicitly uses input redirection.
3.2 Here-Documents (<<): Inline Input
A here-document (or “heredoc”) lets you pass multi-line input to a command directly in the script, using << followed by a delimiter (e.g., EOF). The command reads input until the delimiter is encountered again.
Syntax:
command << DELIMITER
line 1
line 2
...
DELIMITER
Example: Create a greeting.txt file with multi-line content using cat and a heredoc:
cat << EOF > greeting.txt
Hello,
This is a here-document example.
It lets you write multi-line input easily!
EOF
Now greeting.txt contains the three lines above.
3.3 Here-Strings (<<<): Single-Line Input
For single-line input, use a here-string with <<< to pass a string directly to stdin.
Syntax:
command <<< "input_string"
Example: Count characters in the string “Hello” using wc -c:
wc -c <<< "Hello" # Output: 6 (includes newline character)
4. Redirecting Standard Error (stderr)
By default, stderr (file descriptor 2) prints to the terminal, just like stdout. To redirect stderr, explicitly reference its file descriptor (2).
4.1 Redirecting stderr Only with 2> and 2>>
2>: Redirectstderrto a file, overwriting it.2>>: Appendstderrto a file.
Example: Capture errors from a failed ls command into errors.log:
ls non_existent_file 2> errors.log
Now errors.log contains: ls: cannot access 'non_existent_file': No such file or directory.
4.2 Combining stdout and stderr
To redirect both stdout and stderr to the same file, use one of these methods:
Method 1: > output.log 2>&1 (Portable)
Redirect stdout to output.log, then redirect stderr (2) to the same place as stdout (&1).
Example:
command > output.log 2>&1
Method 2: &> output.log (Bash-Specific Shortcut)
Bash 4+ supports &> as a shortcut for > output.log 2>&1.
Example:
command &> output.log # Same as above (Bash-only)
Method 3: Append Both Streams with &>>
To append both stdout and stderr, use &>>:
command &>> output.log # Appends stdout and stderr to output.log
5. Pipes (|): Chaining Commands for Powerful Workflows
A pipe (|) connects the stdout of one command to the stdin of another, creating a “pipeline” of commands. This is one of Bash’s most powerful features, enabling complex data processing with simple, modular commands.
5.1 How Pipes Work
- The
|operator takesstdoutfrom the command on its left and passes it asstdinto the command on its right. - Key Note: Pipes only pass
stdoutby default. To includestderr, redirect it tostdoutfirst (e.g.,command 2>&1 | next_command).
5.2 Practical Pipeline Examples
Example 1: Filter and Sort Files
List all .txt files, filter those containing “report”, and sort them alphabetically:
ls -l | grep ".txt" | grep "report" | sort
Example 2: Count Error Lines in a Log File
Use grep to find “ERROR” lines in app.log, then count them with wc -l:
grep "ERROR" app.log | wc -l
Example 3: Process and Format Data
Extract the 2nd column from data.csv, sort it numerically, and show unique values:
cut -d ',' -f 2 data.csv | sort -n | uniq
5.3 Limitations of Pipes
- Pipes only pass
stdout(redirectstderrwith2>&1if needed). - They are unidirectional (left to right).
- They are temporary and exist only for the duration of the pipeline.
6. Named Pipes (FIFOs): Persistent Inter-Process Communication
Unlike regular pipes (|), which are temporary and die when the pipeline ends, named pipes (or “FIFOs”) are persistent files on disk that enable communication between unrelated processes.
6.1 Creating Named Pipes with mkfifo
Use mkfifo to create a named pipe:
mkfifo my_pipe # Creates a FIFO file named "my_pipe"
6.2 Using Named Pipes for IPC
Named pipes block until data is read/written. For example:
-
In Terminal 1: Write data to the pipe (blocks until read):
echo "Hello from Terminal 1" > my_pipe -
In Terminal 2: Read data from the pipe (unblocks Terminal 1):
cat my_pipe # Output: Hello from Terminal 1
Use case: Communicate between two scripts or processes running in separate shells.
7. Advanced Redirection Techniques
7.1 Process Substitution (<() and >())
Process substitution lets you treat the output of a command as a temporary file, using <() (input) or >() (output). This is useful for commands that expect file inputs.
Example 1: Compare Sorted Files Without Temporary Files
Use diff to compare the sorted versions of file1 and file2, without creating intermediate sorted files:
diff <(sort file1) <(sort file2)
Example 2: Feed Output of One Command to Another as Input
Count the number of lines in the combined output of ls dir1 and ls dir2:
wc -l < <(ls dir1; ls dir2)
7.2 The tee Command: Split Output
The tee command splits stdout into two streams: one to a file and one to the terminal (or another command).
Syntax:
command | tee output_file # Writes to output_file and stdout
Example: Save ls -l output to files.txt and print it to the terminal:
ls -l | tee files.txt
Example with Pipes: Chain tee to save intermediate output:
grep "ERROR" app.log | tee errors.tmp | wc -l # Saves errors to errors.tmp and counts lines
8. Practical Bash Script Examples
Let’s combine pipes and redirection in real-world scripts.
8.1 Example 1: Log File Analyzer
A script that processes a log file, filters errors, counts them, and generates a report.
#!/bin/bash
# log_analyzer.sh: Analyze app.log for errors and generate a report
LOG_FILE="app.log"
REPORT="error_report_$(date +%Y%m%d).txt"
# Check if log file exists
if [ ! -f "$LOG_FILE" ]; then
echo "Error: $LOG_FILE not found!" >&2 # Redirect error message to stderr
exit 1
fi
# Generate report
{
echo "Error Report - $(date)"
echo "======================"
echo "Total errors found: $(grep -c "ERROR" "$LOG_FILE")"
echo -e "\nRecent errors (last 5):"
grep "ERROR" "$LOG_FILE" | tail -n 5
} > "$REPORT" # Redirect entire block output to report
echo "Report generated: $REPORT"
How it works:
- Uses
grep -cto count errors. - Redirects a multi-line block (
{ ... }) to the report file. - Sends critical errors (e.g., “file not found”) to
stderrwith>&2.
8.2 Example 2: Config File Generator with Heredoc
A script that uses a heredoc to generate a Nginx config file dynamically.
#!/bin/bash
# generate_nginx_config.sh: Create a custom Nginx config
DOMAIN="example.com"
PORT=8080
CONFIG_FILE="/etc/nginx/sites-available/$DOMAIN.conf"
# Use heredoc to write config with variables
cat << EOF > "$CONFIG_FILE"
server {
listen $PORT;
server_name $DOMAIN www.$DOMAIN;
root /var/www/$DOMAIN;
index index.html;
access_log /var/log/nginx/$DOMAIN.access.log;
error_log /var/log/nginx/$DOMAIN.error.log;
}
EOF
echo "Config file created at $CONFIG_FILE"
How it works:
- Uses variables (
DOMAIN,PORT) in the heredoc to dynamically set values. - Redirects the heredoc output to the Nginx config file.
9. Common Pitfalls and Best Practices
9.1 Pitfalls to Avoid
- Accidental Overwrites: Using
>on an existing file erases its content. Use>>to append instead. - Confusing File Descriptors: Mixing up
2>&1(redirect stderr to stdout) with1>&2(redirect stdout to stderr). - Pipes Ignore stderr: By default, pipes only pass
stdout. Use2>&1 |to include stderr. - Forgetting Heredoc Delimiters: Ensure the closing delimiter (e.g.,
EOF) has no leading/trailing whitespace.
9.2 Best Practices
- Use
set -o noclobber: Prevent accidental overwrites with>by enablingnoclobber(use>|to force overwrite if needed).set -o noclobber # Enable protection echo "Safe" > file.txt # Fails if file.txt exists echo "Force" >| file.txt # Overwrites despite noclobber - Log to Both File and Stdout: Use
teefor scripts to log output to a file and show it on the terminal. - Check File Existence: Validate input files before redirecting (e.g.,
[ -f "file.txt" ] || exit 1).
10. Conclusion
Pipes and redirection are foundational to Bash scripting, enabling you to manipulate input/output streams, chain commands, and build powerful automation tools. From simple tasks like saving command output to files, to complex workflows like log analysis or inter-process communication, these tools transform Bash from a basic shell into a robust scripting language.
The key to mastery is practice: experiment with redirecting streams, chaining pipes, and building scripts that solve real problems. With these skills, you’ll write more efficient, flexible, and maintainable Bash scripts.