Streams & Redirection: stdin, stdout, stderr, and the Pipe
- Name the three standard streams (stdin 0, stdout 1, stderr 2) and explain why they're kept separate
- Redirect output with >, >>, 2>, and &>, and read input from a file with <
- Explain what a pipe actually connects (stdout → stdin) and why stderr isn't carried
- Merge streams with 2>&1, and turn a list of items into commands with xargs / find -exec
Every command has three wires
Picture stdin, stdout, and stderr as three separate channels — the idea the whole lesson rests on.
Here's the one mental model that makes redirection and pipes suddenly obvious — get this and the rest is syntax.
Every program, the instant it starts, has three wires already plugged in. Think of a command as a little machine with an in socket and two out sockets:
- stdin (0) — the in wire, where it reads input from. By default that's your keyboard.
- stdout (1) — the out wire for normal results. By default, your screen.
- stderr (2) — a second out wire, just for errors and diagnostics. Also your screen, by default.
Those numbers (0, 1, 2) are the streams' file descriptors — the shell refers to each wire by its number, which is exactly how you'll aim them in a moment.
The part that trips people up — and the part that matters most: stdout and stderr are two separate wires that merely happen to both point at your screen. They look like one stream because both land in the same place, but they're independent. And because they're independent, you can send the real results one direction and the error noise another: keep the answer, bin the complaints. That single fact is the reason
>and2>exist and why they're so useful.Redirection: aim the wires at files
Re-point each stream from the screen to a file (or from a file into a command).
Redirection is unplugging a wire from the screen and plugging it into a file instead. The shell does this around the command — the program has no idea it happened, which is why redirection works with every command ever written:
- `> file` — send stdout to file, overwriting whatever was there.
- `>> file` — send stdout to file, appending to the end.
- `2> file` — send stderr to file (that
2is stderr's descriptor from the last step). - `&> file` (or the older
> file 2>&1) — send both stdout and stderr to file. - `< file` — feed stdin from file instead of the keyboard.
Hold onto one warning we'll come back to:
>wipes the file's old contents before the command even runs. When you mean "add to," reach for>>.grep root /etc/passwd > users.txt 2> errors.txt; cat users.txtroot:x:0:0:root:/root:/bin/bash
- > users.txt
- stdout → users.txt (overwrite)
- 2> errors.txt
- stderr → errors.txt
Two redirections on one line: the matching lines (stdout) land in users.txt, any error (stderr) lands inerrors.txt, and nothing prints to the screen — until wecatthe file back to see it worked. Noticegrepitself is completely unchanged; it doesn't know or care where its wires point. Aiming them is the shell's job, not the command's.echo 'first line' > note.txt && echo 'second line' >> note.txt && cat note.txtfirst line second line
- >
- overwrite the file with this output
- >>
- append this output to the end of the file
The overwrite-vs-append distinction, shown: >createsnote.txtwith the first line, then>>adds the second instead of replacing it, so both survive. Swap that second>>for a>and you'd be left with onlysecond line— the whole point of the next callout.What a pipe actually connects
See that | wires one command's stdout into the next's stdin — and that stderr is left behind.
A pipe is the same idea as redirection, but instead of pointing a wire at a file, it points it straight at another command.
A | Bplugs A's stdout directly into B's stdin — no temporary file, the data just streams through memory from one to the next.This is the secret behind every pipeline you've admired: each small tool reads stdin, does one job, writes stdout, and the pipe snaps them together like plumbing.
grep … | cut … | sort … | uniqis four machines wired out-to-in, out-to-in.And here's the consequence almost everyone misses at first: a pipe carries stdout only. A's errors still spill onto your screen — they do not flow into B. So if B needs to see A's error lines too, you have to merge them onto the stdout wire first, with
2>&1.ls /etc /nope 2>&1 | grep -c cannot1
- 2>&1
- redirect stderr (2) into wherever stdout (1) currently points
- grep -c
- count matching lines instead of printing them
2>&1merges stderr into stdout before the pipe, sogrepreceives thels: cannot access '/nope'error line and counts it — giving1. Drop the2>&1and that error would skip the pipe entirely, print to your screen, andgrepwould count0. Position matters:2>&1sits right after the command and before the|, because it means "send wire 2 wherever wire 1 is pointing right now."Check yourself
A pipe (|) feeds the LEFT command's stdout into the right command's what?
Turning a list into commands: xargs and find -exec
Feed a stream of items as ARGUMENTS to another command.
Pipes feed data as stdin, but a lot of commands —
rm,cp,wc,chmod— don't read stdin; they want their targets as arguments on the command line. Sofind . -name '*.tmp' | rmdoes nothing useful, becausermignores stdin. Two tools bridge that gap between "a list of items" and "arguments to a command":- `… | xargs CMD` — gather the incoming lines and hand them to CMD as arguments.
find . -name '*.tmp' | xargs rmactually deletes them. - `find … -exec CMD {} +` —
findruns CMD itself, substituting each match for{}. No pipe at all.
When filenames might contain spaces, prefer the space-safe forms
find … -print0 | xargs -0 …or-exec … {} +. For a quick one-off list, plainxargsreads the clearest.find /var/log -name '*.log' | xargs wc -l | tail -11428 total
- xargs
- build an argument list from stdin, then run the command with it
- -exec … {} +
- find runs the command itself, {} standing in for each match
Read it left to right: findemits matching paths on stdout →xargsturns that list into arguments forwc -l(which counts lines in each file and prints a total) →tail -1keeps just thetotalline. The pipe carried the file list;xargswas the adapter that converted list-into-arguments.find /var/log -name '*.log' -exec wc -l {} +does the identical job without a pipe.- `… | xargs CMD` — gather the incoming lines and hand them to CMD as arguments.
Now do it — plumb some pipes
Read it, then run it. Aim a few wires and build a pipeline in your live terminal.
You have a real, resettable shell to the right. Run these and watch the wires move — where output lands, what the pipe carries, and what redirection captures. This plumbing is the backbone of every script and one-liner you'll ever write, so a couple of minutes here pays off constantly.
echo hello > /tmp/s.txt && echo world >> /tmp/s.txt && cat /tmp/s.txtCreate a file with >, add to it with>>, then read it back. You should see both lines — proof that>>appended instead of clobbering. Try it again with two>and watch only the last line survive.Example — not runnable on your lab box
ls /etc /does-not-exist > /tmp/out.txt 2> /tmp/err.txt; echo '--- out ---'; cat /tmp/out.txt | head -3; echo '--- err ---'; cat /tmp/err.txtSplit the two out-wires into two files: the real listing goes to out.txt (stdout), the 'cannot access' complaint goes to err.txt (stderr). Reading them back separately makes the two-wire model concrete — the results and the error genuinely travelled different paths. cat /etc/passwd | grep -c bashA minimal pipe: cat's stdout becomesgrep's stdin, andgrep -ccounts how many accounts use bash. (Purists would writegrep -c bash /etc/passwdand skip the cat — but seeing the pipe wire two commands together is the point here.)
- Every command has three wires: stdin (0) in, stdout (1) normal output, stderr (2) errors. stdout and stderr are SEPARATE wires — that's what lets you keep results and discard noise.
- Redirect with > (overwrite), >> (append), 2> (stderr), &> or >file 2>&1 (both), < (stdin from a file). > clobbers before the command runs — use >> to build a file up, and never
sort file > file. set -o noclobber makes > refuse to overwrite (>| forces it). For both streams in one file the order is > f 2>&1, never 2>&1 > f. - A pipe wires LEFT stdout → RIGHT stdin and carries stdout ONLY; merge errors onto it with 2>&1 (placed before the |) if the next command needs them.
- Pipes feed stdin, but rm/cp/wc want arguments: xargs turns a stream of items into arguments, and find -exec {} + does the same without a pipe. Use -print0 | xargs -0 (or -exec) when names may contain spaces.
| Syntax | Meaning |
|---|---|
| cmd > f | stdout → f (overwrite) |
| cmd >> f | stdout → f (append) |
| cmd 2> f | stderr → f |
| cmd &> f | stdout AND stderr → f |
| cmd < f | read stdin from f |
| A | B | A's stdout → B's stdin |
| A 2>&1 | B | A's stdout AND stderr → B's stdin |
| … | xargs C | incoming lines become arguments to C |
| diff <(a) <(b) | process substitution: command output as a file |
