Lesson 5 • Beginner
Pipes and Redirection 🔗
By the end of this lesson you'll be able to chain small commands into one powerful pipeline, send output and errors exactly where you want, and combine commands so they run in the right order — the skills that turn the terminal from a toy into a tool.
What You'll Learn
- Understand the three streams: stdin, stdout, and stderr
- Chain commands with the pipe | (list | filter | count)
- Redirect output to files with > (overwrite) and >> (append)
- Capture errors with 2> and merge streams with 2>&1
- Discard output with /dev/null and split it with tee
- Sequence commands with &&, ||, and ;
grep, sort, and wc). These are real shell commands — paste them into the free bash runner linked under each block, or run them in your own terminal (macOS, Linux, or WSL on Windows). The Output panel shows exactly what you should see.| carrying parts between them. Redirection is the loading dock: instead of putting the finished product back on the belt, you send it into a labelled crate (a file) or into the dumpster (/dev/null).1. The Three Streams
Before pipes make sense, you need to know that every command has three channels. stdin (channel 0) is where input arrives. stdout (channel 1) is where normal results go. stderr (channel 2) is a separate channel just for error messages. On screen, stdout and stderr look the same, but keeping them apart is what lets you save real results while still seeing warnings — and it's the reason a later trick (2>&1) exists at all.
# Every command talks through THREE streams (numbered channels):
# stdin (0) — where INPUT comes from (your keyboard by default)
# stdout (1) — where normal OUTPUT goes (your screen by default)
# stderr (2) — where ERROR output goes (your screen by default)
# 'echo' writes to stdout. This text is going to channel 1:
echo "this line is stdout (channel 1)"
# 'ls' on a missing file writes the complaint to stderr (channel 2),
# NOT stdout. On screen they look identical, but they are separate pipes:
ls no-such-file.txtthis line is stdout (channel 1)
ls: cannot access 'no-such-file.txt': No such file or directory2. The Pipe Operator |
The pipe (|) connects the stdout of the command on its left to the stdin of the command on its right. That lets you build a chain of tiny, single-purpose tools — list | filter | count — where data flows left to right. This is the Unix philosophy in action: "do one thing well," then combine. Read this worked example, run it, and watch each stage shrink the data.
# A pipe (|) connects the stdout of one command to the stdin of the
# next, so data flows left -> right through a chain of small tools.
# Build a sample log first so every example below has real data:
cat > server.log <<'EOF'
[INFO] GET /api/users
[ERROR] database timeout
[INFO] POST /api/login
[ERROR] database timeout
[WARN] slow query
[ERROR] invalid token
[ERROR] database timeout
EOF
# ls's list -> grep keeps the matches -> wc -l counts the lines.
# This is the classic three-stage pipe: list | filter | count
ls | grep server | wc -l # 1 (one file matches "server")
echo "--- only the ERROR lines ---"
grep ERROR server.log # grep filters stdin/the file
echo "--- count the ERROR lines ---"
grep ERROR server.log | wc -l # 4 (pass matches into wc -l)
echo "--- most frequent errors ---"
# sort groups duplicates, uniq -c counts each group, sort -rn ranks them:
grep ERROR server.log | sort | uniq -c | sort -rn1
--- only the ERROR lines ---
[ERROR] database timeout
[ERROR] database timeout
[ERROR] invalid token
[ERROR] database timeout
--- count the ERROR lines ---
4
--- most frequent errors ---
3 [ERROR] database timeout
1 [ERROR] invalid tokenPro Tip — Build pipelines one stage at a time
Don't write a five-command pipe in one go. Run grep ERROR server.log, check it looks right, then add | sort, check again, then | uniq -c, and so on. Each | you add only sees the output of the stage before it, so building up gradually makes mistakes obvious immediately.
Your turn. The pipeline below is almost complete — fill in the blank marked ___ using the hint in the comment, then run it.
# 🎯 YOUR TURN — replace each ___ then run it.
# Goal: count how many lines contain the word INFO in server.log.
# 1) Filter the file down to lines containing INFO
# 👉 the tool that filters lines by a pattern is: grep
___ INFO server.log | wc -l # 👉 put the filter tool in the blank
# ✅ Expected output:
# 22___, run it (you'll need the server.log from the example above), and check your output matches the expected 2.3. Redirection: >, >>, <, 2>, 2>&1
Redirection points a stream at a file instead of the screen or keyboard. > writes output to a file and overwrites it; >> appends to the end. < feeds a file in as stdin. To capture errors you must name the channel: 2> redirects stderr on its own, and 2>&1 means "send stderr to wherever stdout is going" — so > file 2>&1 puts both into one file. The order matters: redirect stdout first, then merge stderr into it.
# Redirection sends a stream somewhere other than the screen/keyboard.
# > WRITES output to a file and OVERWRITES whatever was there.
echo "Hello" > file.txt
# >> APPENDS output to the end of a file (keeps existing contents).
echo "World" >> file.txt
cat file.txt # Hello / World on two lines
# 2> redirects ONLY stderr (channel 2). Here stdout still hits the screen,
# but the error is captured into errors.txt instead:
ls file.txt no-such-file.txt 2> errors.txt
echo "--- captured stderr ---"
cat errors.txt
# 2>&1 means "send channel 2 to wherever channel 1 is going" — i.e.
# merge errors INTO normal output. Order matters: redirect stdout first.
echo "--- merge both streams into one file ---"
ls file.txt no-such-file.txt > all.txt 2>&1
cat all.txt
# < feeds a file in as stdin. sort reads its input from fruit.txt:
echo "--- input redirection (sort < file) ---"
printf 'banana\napple\ncherry\n' > fruit.txt
sort < fruit.txtHello
World
--- captured stderr ---
ls: cannot access 'no-such-file.txt': No such file or directory
--- merge both streams into one file ---
ls: cannot access 'no-such-file.txt': No such file or directory
file.txt
--- input redirection (sort < file) ---
apple
banana
cherry4. /dev/null, tee, and Command Chaining
/dev/null is a special file that throws away anything written to it — use it to silence output you don't care about. tee does the opposite of hiding: it splits a stream so it goes to a file and the screen at once. Finally, command chaining sequences whole commands: ; runs the next one regardless, && runs it only if the previous command succeeded, and || runs it only if the previous command failed.
# /dev/null is the "trash can" — anything written to it disappears.
# Run a command but silence its normal output, keep only errors:
ls file.txt no-such-file.txt > /dev/null
echo "(stdout was thrown away; the error above came from stderr)"
# Silence EVERYTHING (stdout and stderr) — a common, quiet check:
grep ERROR server.log > /dev/null 2>&1
echo "grep finished; we showed nothing from it"
# tee splits a stream: it writes to a file AND passes it on to the screen.
echo "--- tee: save and show at once ---"
echo "saved and shown" | tee output.txt
# Command chaining operators run commands in sequence:
# ; run the next command no matter what
# && run the next command ONLY if the previous one SUCCEEDED
# || run the next command ONLY if the previous one FAILED
echo "--- chaining ---"
mkdir build && echo "made build/ (only printed because mkdir worked)"
ls missing-dir || echo "ls failed, so this fallback ran"
echo "first" ; echo "second (always runs)"ls: cannot access 'no-such-file.txt': No such file or directory
(stdout was thrown away; the error above came from stderr)
grep finished; we showed nothing from it
--- tee: save and show at once ---
saved and shown
--- chaining ---
made build/ (only printed because mkdir worked)
ls: cannot access 'missing-dir': No such file or directory
ls failed, so this fallback ran
first
second (always runs)Now you try. Fill in the two blanks with the right chaining operators using the hints, then run it.
# 🎯 YOUR TURN — replace each ___ then run it.
# Goal: print "ok" ONLY if the file server.log exists, and run a
# fallback message ONLY if the previous check failed.
# 'test -f FILE' succeeds when the file exists (exit code 0).
# 1) Run echo "ok" only if the test SUCCEEDS -> use the AND operator
test -f server.log ___ echo "ok" # 👉 the "only if it worked" operator
# 2) Run the fallback only if the test FAILS -> use the OR operator
test -f ghost.log ___ echo "missing" # 👉 the "only if it failed" operator
# ✅ Expected output:
# ok
# missingok
missing___ with the correct operator, run it, and check your output against the expected lines.Common Errors (and the fix)
>wiped out my file. A single>overwrites — it erases the file before writing. To add to a file without losing what's there, use>>. Treat>on an important file as a delete.- My error message still showed on screen after
command > log.txt. Plain>only captures stdout. Errors are on stderr — capture them with2> log.txt, or grab both withcommand > log.txt 2>&1. - "ambiguous redirect" or the merge didn't work. Order matters: write
> file 2>&1, not2>&1 > file. The2>&1copies wherever stdout points right now, so redirect stdout first. - Tried to pipe into a filename:
grep ERROR | log.txt. A pipe|needs a command on its right, not a file. To send to a file use redirection:grep ERROR server.log > log.txt. cat file | grep xworks, but feels wasteful. It is — this is the "useless use ofcat."grepcan read the file directly:grep x file. Pipes are for chaining commands, not for feeding a single file in.
📋 Quick Reference
| Operator | What it does |
|---|---|
| cmd1 | cmd2 | Send cmd1's stdout into cmd2's stdin |
| > file | Redirect stdout to a file (overwrite) |
| >> file | Redirect stdout to a file (append) |
| < file | Feed a file in as stdin |
| 2> file | Redirect only stderr to a file |
| > file 2>&1 | Redirect both stdout and stderr to a file |
| > /dev/null 2>&1 | Discard all output (silence everything) |
| | tee file | Write to a file AND show on screen |
| cmd1 && cmd2 | Run cmd2 only if cmd1 succeeded |
| cmd1 || cmd2 | Run cmd2 only if cmd1 failed |
| cmd1 ; cmd2 | Run cmd2 after cmd1, regardless |
Frequently Asked Questions
Q: What is the difference between a pipe (|) and redirection (>)?
A pipe sends one command's output to ANOTHER command as its input (cmd1 | cmd2). Redirection sends output to or from a FILE (cmd > file.txt, or cmd < file.txt). Rule of thumb: a pipe always has a command on both sides; redirection has a filename on one side.
Q: Why didn't > capture my error message?
Plain > only redirects stdout (channel 1). Error messages travel on stderr (channel 2), so > leaves them on screen. Capture them with 2> file.txt, or merge both into one place with > file.txt 2>&1.
Q: When do I use > versus >>?
> overwrites the file — it erases the old contents first, so it's a common cause of accidental data loss. >> appends to the end and keeps what was already there. If in doubt and the file matters, use >>.
Q: What does 2>&1 actually mean?
It means 'send channel 2 (stderr) to wherever channel 1 (stdout) is currently going.' Put it AFTER you redirect stdout: command > file.txt 2>&1 sends both to the file. The shorthand &> file.txt does the same in Bash.
Q: What is /dev/null for?
It's a special file that discards everything written to it — the system 'trash can.' Use it to silence output you don't care about: command > /dev/null hides normal output, and command > /dev/null 2>&1 silences both output and errors.
Q: What is the difference between && and ; when chaining commands?
; runs the next command no matter what happened before. && runs the next command only if the previous one SUCCEEDED (exit code 0). Use && to stop a chain when a step fails, e.g. cd build && make.
Mini-Challenge: A Tiny Error Report
No blanks this time — just a brief and an outline to keep you on track. Combine a pipe, tee, and && into a single line, run it, and check your output against the comments. This is exactly the kind of one-liner you'll reach for on real servers.
# 🎯 MINI-CHALLENGE: build a tiny error report
# Using the server.log from earlier, in ONE chained line:
# 1. Keep only the ERROR lines (hint: grep)
# 2. Count how many there are (hint: | wc -l)
# 3. Save that number to error-count.txt AND show it on screen (hint: | tee)
# 4. Then, ONLY if that worked, echo "report saved" (hint: && )
#
# ✅ Expected output (screen):
# 4
# report saved
# ✅ error-count.txt should contain: 4
# your one-line pipeline here4
report saved4 then report saved, and leave 4 inside error-count.txt.🎉 Lesson Complete!
- ✅ Every command has three streams: stdin (0), stdout (1), stderr (2)
- ✅ The pipe
|feeds one command's stdout into the next command's stdin - ✅
>overwrites a file,>>appends, and<reads a file as stdin - ✅
2>captures errors;2>&1merges stderr into stdout - ✅
/dev/nulldiscards output;teesaves and shows at the same time - ✅
&&(on success),||(on failure), and;(always) sequence commands - ✅ Next lesson: Shell Scripting — wrap these pipelines into reusable scripts and automate them
Sign up for free to track which lessons you've completed and get learning reminders.