Stop Typing the Same Commands Twice
When you first open a Linux terminal, every command feels like an island. You run ls to see files, cat to read them, and grep to search them. You copy something from one output, paste it into another tool, and hit Enter.
That manual copying gets old fast.
The real power of Linux comes from the Unix philosophy: write programs that do one thing well, and make them work together. Pipes (|) and redirection (>, <, >>) are the glue that joins those small utilities into fast, custom workflows.
If you are a web developer, a DevOps novice, or a programmer looking to get comfortable on a server, this guide will make standard streams click once and for all.
Part 1: The Three Standard Streams
Every single command you run in Bash automatically opens three data channels:
| Stream Name | File Descriptor | Default Source / Destination | What It Does |
|---|---|---|---|
Standard Input (stdin) |
0 |
Keyboard | Data flowing into the command |
Standard Output (stdout) |
1 |
Terminal Screen | Normal output from the command |
Standard Error (stderr) |
2 |
Terminal Screen | Error messages produced by the command |
By default, both output (stdout) and errors (stderr) print directly to your screen. Redirection simply means telling Linux: “Do not send this to the screen; send it to a file or another device instead.”
Here are the redirection operators you will use daily:
| Operator | Meaning | Example |
|---|---|---|
> |
Redirect stdout to a file (overwrites existing content) | echo "hi" > file.txt |
>> |
Append stdout to a file (preserves existing content) | echo "again" >> file.txt |
< |
Read stdin from a file instead of the keyboard | sort < names.txt |
2> |
Redirect stderr only | ls fake 2> error.log |
&> or 2>&1 |
Redirect both stdout and stderr together | npm run build &> build.log |
\| |
Send stdout of one command to stdin of another | cat names.txt \| sort |
Part 2: Redirection Basics (Examples 1 to 5)
Let’s walk through concrete examples you can run right now on your machine.
Example 1: Overwriting a file with >
The > operator takes the standard output of a command and writes it to a file. If the file already exists, Bash wipes it clean first.
echo "Server status: healthy" > status.txt
cat status.txt
If you run echo "Server status: degraded" > status.txt, the word “healthy” is gone, replaced entirely by “degraded”.
Example 2: Appending to a file with >>
When you want to log events without destroying prior entries, use double arrows (>>).
echo "Backup started: $(date)" >> backup.log
sleep 1
echo "Backup finished: $(date)" >> backup.log
cat backup.log
The file now contains both lines.
Example 3: Silencing or capturing errors with 2>
If you run a command that fails, it writes to stream 2 (stderr). Normal redirection with > will not catch it.
# This prints an error to your terminal screen:
ls /root/secret_folder > output.txt
# This catches the error in errors.log instead:
ls /root/secret_folder 2> errors.log
cat errors.log
If you just want to throw errors away completely, point descriptor 2 to the Linux “black hole” (/dev/null):
ls /root/secret_folder 2> /dev/null
Example 4: Capturing both output and errors (&>)
When running automated scripts, you usually want one log file containing everything that happened—both normal messages and errors.
# Modern Bash syntax
ping -c 1 127.0.0.1 &> network.log
ping -c 1 invalid.domain.example &>> network.log
cat network.log
Alternatively, you will often see the classic portable POSIX syntax:
command > network.log 2>&1
This reads: “Send standard output to network.log, then direct file descriptor 2 (stderr) to wherever file descriptor 1 (stdout) is currently pointing.”
Example 5: Feeding files into commands with <
Instead of having a command ask you for keyboard input, you can feed a file directly into its stdin:
# Create a small list
echo -e "banana\napple\ncherry" > fruits.txt
# Sort reads the file via stdin
sort < fruits.txt
Part 3: Connecting Tools with Pipes (Examples 6 to 7)
Redirection points data toward or away from files. A pipe (|) points data toward another command.
The pipe takes the stdout of the command on the left and connects it directly to the stdin of the command on the right.
[ Command A ] --(stdout)--> | PIPE | --(stdin)--> [ Command B ]
Example 6: Counting items with wc -l
Need to know how many .log files live in /var/log (or your current folder)? Do not count them by hand:
ls -1 | wc -l
ls -1 lists one file per line; wc -l reads those lines from its standard input and counts them.
Example 7: Filtering with grep
Instead of opening a giant file or reading endless console output, pipe it into grep:
# View only the lines containing your username
cat /etc/passwd | grep "$USER"
(Tip: While grep "$USER" /etc/passwd also works directly, using a pipe is invaluable when the input comes from live terminal tools like ps, docker, or git.)
Hands-on Example: Real-World Pipelines (Examples 8 to 10)
Let’s build a mini log-analysis scenario. Copy and paste this block into your terminal to generate a sample web server log:
cat << 'EOF' > access.log
192.168.1.10 - GET /index.html 200
192.168.1.15 - POST /login 401
192.168.1.10 - GET /dashboard 200
10.0.0.5 - GET /index.html 200
192.168.1.15 - POST /login 200
192.168.1.20 - GET /about 404
10.0.0.5 - GET /api/v1/data 500
192.168.1.10 - GET /logout 200
EOF
Now let’s extract meaningful data using chained pipes.
Example 8: Finding unique visitors (sort | uniq)
The uniq command only collapses duplicates that are right next to each other, so you always sort the data first.
Let’s pull the IP addresses (the first column) using cut, sort them, count occurrences, and rank them:
cut -d ' ' -f 1 access.log | sort | uniq -c | sort -nr
Output:
3 192.168.1.10
2 192.168.1.15
2 10.0.0.5
1 192.168.1.20
You just built a mini analytics engine in 47 characters.
Example 9: Splitting streams with tee
Sometimes you want to see output on your screen and save it to a file at the exact same moment. tee duplicates the stream:
grep "401" access.log | tee unauthorized_access.txt
You will see the matching line on your monitor, but if you run cat unauthorized_access.txt, the data is saved there too.
Example 10: The complete incident report pipeline
Let’s combine pipes, grep, sorting, and file redirection into a single pipeline. We will find every client that triggered a server error (HTTP codes in the 400s or 500s) and save an audit trail:
grep -E " (4[0-9]{2}|5[0-9]{2}) " access.log \
| cut -d ' ' -f 1,4,5 \
| sort \
> incident_report.txt
cat incident_report.txt
Output:
10.0.0.5 GET /api/v1/data 500
192.168.1.15 POST /login 401
192.168.1.20 GET /about 404
Notice how > sat at the very end of the pipeline. The data flowed through three separate utilities in memory before finally hitting the disk.
Common Mistakes / Troubleshooting
- Wiping a file while trying to read it:
Running
cat data.txt | grep "active" > data.txtwill result in an empty file. The shell opens and emptiesdata.txtwith>beforecathas a chance to read it. Use a temporary file instead:grep "active" data.txt > data.tmp && mv data.tmp data.txt - Placing
2>&1in the wrong position: The order matters. Runningcommand 2>&1 > file.logsends stderr to the terminal and stdout to the file. Always put the redirection target first:command > file.log 2>&1 - Confusing pipes (
|) with redirects (>): Pipes send data to a command. Redirection sends data to a file.- Incorrect:
ls | output.txt(the shell tries to executeoutput.txtas a program). - Correct:
ls > output.txt.
- Incorrect:
- Assuming pipes pass errors:
A standard pipe
|only passesstdout. If the command on the left fails with an error, that error prints to your screen and skips the pipe. In modern Bash, use|&to pipe both stdout and stderr together.
Try It Yourself
Write a single-line command pipeline that:
- Lists all running processes using
ps aux. - Filters for processes matching the word
root. - Counts how many there are.
- Appends that final number to a file called
root_process_count.txt.
Click here to reveal solution
```bash ps aux | grep "^root" | wc -l >> root_process_count.txt ``` *(Using `^root` ensures you match processes owned by the user root at the start of the line rather than matching the letters "root" inside arbitrary command arguments).*What’s Next
- Process Substitution (
<()): Learn how to treat the output of a command as a virtual file (e.g.,diff <(ls dirA) <(ls dirB)). - Text Processing Powerhouses: Take the output from your pipes and manipulate it using
awkandsed. - Exit Codes and
$?: Learn how to check whether piped commands succeeded using thePIPESTATUSarray.
Bookmark this page so you have a quick cheat sheet next time you need to tame terminal output.