Pipes and Redirection
Part of Essentials
This article assumes you're comfortable with basic commands and command chaining (&&, ||). Pipes are related but different ā they connect command output to command input, not just sequence execution.
The most powerful Linux one-liners you'll ever see don't use a single complex command. They use simple commands connected together. cat access.log | grep "500" | awk '{print $1}' | sort | uniq -c | sort -nr | head -10 ā that's ten minutes of investigation distilled to one line, and each part is a simple tool you already know.
That's the Unix philosophy in action: programs that do one thing well, connected by pipes. This article shows you how to connect them.
Where You've Seen This
If you've used PowerShell, you know pipes ā Get-Process | Where-Object CPU -gt 10 | Sort-Object CPU -Descending. The Linux version works the same way conceptually, but passes plain text instead of objects. That difference matters: Linux pipelines are more composable (any tool can talk to any other tool), but you work with structured text rather than typed objects.
If you've built ETL pipelines, data workflows, or streaming architectures ā Kafka, Spark, Logstash ā the stdin/stdout model is the same idea at shell scale. Data flows from source through transformations to sink. Each command is a transform.
Redirection (>, >>, 2>) is the command-line equivalent of writing to a file in any language. You've done this before; the Linux operators are just a more concise syntax.
Standard Streams: The Foundation
Every process in Linux has three data channels open by default:
graph LR
STDIN["š„ stdin (0)\nStandard Input\nā Keyboard or pipe"] --> PROCESS["āļø Command\nor Process"]
PROCESS --> STDOUT["š¤ stdout (1)\nStandard Output\nā Screen or redirect"]
PROCESS --> STDERR["ā ļø stderr (2)\nStandard Error\nā Screen or redirect"]
style STDIN fill:#2d3748,stroke:#63b3ed,stroke-width:2px,color:#fff
style PROCESS fill:#d69e2e,stroke:#cbd5e0,stroke-width:2px,color:#000
style STDOUT fill:#2d3748,stroke:#68d391,stroke-width:2px,color:#fff
style STDERR fill:#2d3748,stroke:#fc8181,stroke-width:2px,color:#fff
| Stream | Number | Default | What Goes Here |
|---|---|---|---|
| stdin | 0 | Keyboard | Input to commands |
| stdout | 1 | Terminal | Normal output |
| stderr | 2 | Terminal | Error messages |
By default, stdout and stderr both print to your terminal mixed together. Redirection and pipes give you control over each stream separately.
Redirection: To and From Files
Redirection changes where a stream goes ā from the terminal to a file, or from a file to a command's input.
-
Output Redirection (>)
Why it matters: Capture command output to a file for logging, later processing, or sharing with others.
> Overwrites Without Warning
>will silently overwrite an existing file. There's no confirmation. Ifreport.txtexisted, it's gone.To protect against this in interactive sessions:
-
Append Redirection (>>)
Why it matters: Add to an existing file without overwriting it. The essential operator for log files and accumulating data.
-
Input Redirection (<)
Why it matters: Feed a file into a command's stdin instead of typing. Less common in interactive use, but appears in scripts.
-
stderr Redirection (2>)
Why it matters: Error messages and normal output are separate streams. When you redirect with
>, errors still print to the terminal. Use2>to capture them separately.Redirect stderrfind / -name "hosts" 2>/dev/null # discard errors, keep results find / -name "hosts" 2>errors.txt # save errors to file command > output.txt 2> errors.txt # separate stdout and stderr command > all.txt 2>&1 # (1)! combine both to one file2>&1redirects stderr (2) to wherever stdout (1) is currently going. Order matters:> all.txt 2>&1works;2>&1 > all.txtdoes not (the latter redirects stderr to the terminal, then redirects stdout to the file).
Combining Redirections
# Capture output to file AND show it on screen simultaneously
# (tee reads stdin and writes to both stdout and a file)
find /var/log -name "*.log" | tee found-logs.txt
# Run a long job, save output, discard errors
./long-running-script.sh > results.txt 2>/dev/null
# Save both stdout and stderr to the same file
./deployment.sh > deploy.log 2>&1
# Save stderr to one file, stdout to another
./script.sh > output.log 2> errors.log
Here Documents and Here Strings
For feeding multi-line input to commands:
# Feed multiple lines to a command
cat << EOF > /etc/myapp/config.conf
[database]
host = db-prod-01
port = 5432
name = myapp_db
EOF
# Common for creating config files in scripts
sudo tee /etc/nginx/sites-available/myapp << EOF
server {
listen 80;
server_name myapp.example.com;
root /var/www/myapp;
}
EOF
# Feed a single string to stdin
grep "pattern" <<< "string to search in"
# Practical: feed a variable to a command expecting stdin
base64 <<< "encode this text"
Pipes: Command to Command
A pipe (|) takes the stdout of the left command and feeds it as stdin to the right command. No intermediate file, no waiting ā it's streaming.
ls -lh /var/log/ | less
# ls outputs the directory listing
# | sends it to less
# less lets you scroll through it
Building Pipelines
Pipelines chain as many commands as you need:
# Start simple
cat /var/log/nginx/access.log
# Filter to 500 errors only
cat /var/log/nginx/access.log | grep " 500 "
# Extract just the IP addresses
cat /var/log/nginx/access.log | grep " 500 " | awk '{print $1}'
# Sort them
cat /var/log/nginx/access.log | grep " 500 " | awk '{print $1}' | sort
# Count occurrences of each IP
cat /var/log/nginx/access.log | grep " 500 " | awk '{print $1}' | sort | uniq -c
# Sort by count, most first
cat /var/log/nginx/access.log | grep " 500 " | awk '{print $1}' | sort | uniq -c | sort -nr
# Show only the top 10
cat /var/log/nginx/access.log | grep " 500 " | awk '{print $1}' | sort | uniq -c | sort -nr | head -10
This is how investigation pipelines get built ā one stage at a time, checking the output at each step.
The Essential Pipeline Tools
These commands exist primarily to work within pipelines:
-
grep ā Filter Lines
Filter a stream to only lines matching a pattern. Covered in depth in the grep article.
-
sort ā Sort Lines
Sort lines alphabetically or numerically.
-
uniq ā Remove Duplicates / Count
Remove consecutive duplicate lines, or count their occurrences. Works best after
sort. -
wc ā Count
Count lines, words, or characters.
-
awk ā Extract Fields
Extract specific columns from structured text. The most powerful field processor in the pipeline toolkit.
-
cut ā Extract Columns
Extract specific columns or character positions from fixed-format output.
-
tee ā Split Output
Write to a file AND pass through to the next pipe. Essential when you want to save intermediate results.
-
head / tail ā Limit Output
Take only the first or last N lines. Ubiquitous at the end of pipelines.
stderr in Pipelines
A critical detail: pipes only carry stdout. stderr bypasses the pipe and goes directly to the terminal.
find / -name "*.conf" | wc -l
# Errors print to screen; only successful results go to wc
To include stderr in a pipeline:
find / -name "*.conf" 2>&1 | wc -l # count everything including error lines
find / -name "*.conf" 2>/dev/null | wc -l # suppress errors, count only results
Real-World Pipeline Patterns
Find the most common errors in the past hour:
# What IP addresses are causing the most 404s?
grep " 404 " /var/log/nginx/access.log \
| awk '{print $1}' \
| sort | uniq -c \
| sort -nr \
| head -10
# How many errors per hour in the last day?
journalctl --since "24 hours ago" --until now \
| grep -i "error" \
| awk '{print $1, $2, substr($3,1,2)}' \
| sort | uniq -c
Find what's consuming resources:
Find what's eating disk:
# Largest directories in /var
du -sh /var/* 2>/dev/null | sort -hr | head -10
# Largest files anywhere on the system
find / -type f -size +100M 2>/dev/null \
| xargs ls -lh 2>/dev/null \
| sort -k5 -hr \
| head -10
# Which log files grew today?
find /var/log -name "*.log" -newer /var/log -type f \
| xargs ls -lh 2>/dev/null \
| sort -k5 -hr
Process configuration files:
# Find all uncommented settings in a config file
grep -v "^#" /etc/ssh/sshd_config | grep -v "^$"
# Find all unique setting names
grep -v "^#" /etc/ssh/sshd_config \
| grep -v "^$" \
| awk '{print $1}' \
| sort -u
# Check if a setting is enabled
grep -v "^#" /etc/ssh/sshd_config | grep "PermitRootLogin"
Quick Reference
Redirection Operators
| Operator | What It Does | Example |
|---|---|---|
> |
Redirect stdout to file (overwrite) | ls > files.txt |
>> |
Redirect stdout to file (append) | echo "line" >> log.txt |
< |
Redirect file to stdin | sort < names.txt |
2> |
Redirect stderr to file | find / 2>/dev/null |
2>&1 |
Redirect stderr to stdout | command > all.txt 2>&1 |
&> |
Redirect both stdout and stderr | command &> all.txt |
\| |
Pipe stdout to next command | ls \| grep ".conf" |
\| tee |
Write to file and pass through | cmd \| tee file.txt \| wc -l |
Pipeline Toolkit
| Command | What It Does in Pipelines |
|---|---|
grep pattern |
Keep only lines matching pattern |
grep -v pattern |
Remove lines matching pattern |
sort |
Sort lines alphabetically |
sort -n |
Sort numerically |
sort -hr |
Sort human-readable sizes, largest first |
uniq |
Remove consecutive duplicates |
uniq -c |
Count occurrences |
wc -l |
Count lines |
head -N |
Keep first N lines |
tail -N |
Keep last N lines |
awk '{print $N}' |
Extract Nth field |
cut -d: -f1 |
Extract field by delimiter |
tee file.txt |
Write to file and pass through |
Practice Exercises
Exercise 1: Build a Log Analysis Pipeline
Using /var/log/auth.log (or /var/log/secure on RHEL), build a pipeline that:
- Shows only lines containing "Failed"
- Extracts the source IP address (the IP after "from" in the line)
- Counts how many failed attempts per IP
- Sorts by count, highest first
- Shows the top 5 offenders
Exercise 2: Redirect and Tee
Run a disk space analysis (du -sh /var/* 2>/dev/null) that:
- Saves the complete output to
/tmp/disk-report.txt - Simultaneously shows only entries larger than 1GB on the terminal
Exercise 3: Separate stdout and stderr
Run find / -name "sshd_config" and:
- Save only the successful results (stdout) to
/tmp/found.txt - Discard all "Permission denied" errors (stderr)
- Print the count of found files to the terminal
Quick Recap
- Three streams: stdin (0), stdout (1), stderr (2) ā redirect each independently
>overwrites;>>appends ā the most common mistake is using>when you meant>>2>/dev/nullā silence errors; ubiquitous infindand other commands that hit permission-denied directories2>&1ā merge stderr into stdout; essential when capturing all output to a file|pipes only stdout ā to include stderr, add2>&1before the pipe- Build pipelines incrementally ā add one stage at a time, verify output, then extend
- Core toolkit:
grep,sort,uniq -c,wc -l,head,tail,awk,tee
Further Reading
Command References
man bashā the "Redirection" section covers every redirect operatorman teeā the tee command in detailman awkā the full awk reference (alsoman gawkfor GNU awk)man sortā sort options including locale-aware and stable sortingman uniqā uniq options
Deep Dives
- The Art of Command Line: Data Wrangling ā practical pipeline patterns
- Bash Redirections Cheat Sheet ā Bash Hackers Wiki on redirection
- Unix Philosophy ā why pipes exist and why they work
Official Documentation
- GNU Coreutils Manual ā sort, uniq, wc, tee, head, tail
- GNU Bash Manual: Redirections ā complete redirect reference
What's Next?
Pipes flow data between commands, and the most common thing you'll do with that data is search it. grep is the workhorse of Linux text processing ā and it deserves its own deep dive.
Head to grep to learn regular expressions, recursive searching, context flags, and the patterns that turn grep from a simple filter into a powerful investigation tool.