Linux & Bash · Part I · Module 12 · Streams, Redirection and Pipes
Three Streams: stdin, stdout and stderr
Output and errors look like one thing on your screen because they both arrive there. They are not one thing. Underneath, every process is born with three separate channels, and keeping results apart from complaints about results is the decision that makes the rest of this module possible.
Foundation17 min readLinuxPart IFundamentalsStreamsRedirection
Part I · Module 12 · Chapter 12.1 · Three Streams: stdin, stdout and stderr
Chapter 7.1 listed five things a process carries and mentioned open files in a sentence, promising a module. This is it.
1. Three Channels, Not One
Every process starts life with three streams already open. They have numbers and names:
| Number | Name | Carries |
|---|---|---|
0 | standard input, stdin | What the process reads |
1 | standard output, stdout | Its ordinary results |
2 | standard error, stderr | Its error messages and diagnostics |
The numbers matter because that is how you refer to them in the operators that fill the rest of this module. The names matter because that is how everyone talks about them.
By default all three are connected to your terminal. That is why they look like one thing: input comes from your keyboard, and both output streams arrive on the same screen, interleaved in the order they were written.
They are not one thing, and the next section proves it.
2. Proof That They Are Separate
ls given two arguments, one good and one bad, writes to both streams:
$ ls /tmp /nope
ls: /nope: No such file or directory
/tmp:
agent.sock build scratchOn screen that is a single lump of text. Send each stream to a different file and the lump comes apart:
$ ls /tmp /nope > out.txt 2> err.txt
$ cat out.txt
/tmp:
agent.sock build scratch
$ cat err.txt
ls: /nope: No such file or directoryThe listing went one way and the complaint went the other. ls wrote to two different places without being asked to; it always does.
Discard either one and the other survives untouched:
$ ls /tmp /nope > /dev/null
ls: /nope: No such file or directory
$ ls /tmp /nope 2> /dev/null
/tmp:
agent.sock build scratch3. Which Stream Does What
The convention, stated plainly:
Stream two — results. The thing the command was asked to produce. A file listing, a line count, matched lines, a compiled file name. Anything another program might reasonably want to consume.
Stream three — everything else. Error messages, warnings, progress bars, "3 of 47 files processed", prompts. Anything meant for a person rather than for the next program in a pipeline.
That second list surprises people. Progress output goes to the error stream, and it is not an error. The test is not "is this bad news?" but "would the next program in a pipeline want this?"
$ curl -s -o file.iso https://example.com/file.isocurl's progress meter goes to stream three precisely so that curl url | tar xz works — the archive flows down the pipe and the percentages go to your screen.
4. Writing to Each Stream Yourself
In a script, echo writes to stream two. To write to stream three — which is where your error messages belong — redirect it:
echo "starting run $run_name" # results
echo "warning: no filelist found" >&2 # diagnostics>&2 is Chapter 12.3's notation and is worth adopting immediately. It matters for a reason that is easy to miss:
$ ./myscript.sh > run.logAnything the script wrote with plain echo is now in the file and invisible. Anything it wrote with >&2 is still on your screen. If your error messages are not on stream three, the person running your script in a pipeline will never see them.
The rule for a script worth handing to someone else:
| What you are printing | Where it goes |
|---|---|
| The answer | Stream two — plain echo |
| Errors, warnings, progress, prompts | Stream three — >&2 |
5. Input
Stream one is where a command reads from, and it has two modes that behave very differently.
Given a filename, most commands read the file:
$ wc -l names.txt
3 names.txtGiven nothing, they read stream one — which is your keyboard by default:
$ wc -l
hello
there
^D
2Ctrl-D sends end-of-input, which is how you tell a command reading from the keyboard that you have finished. (Chapter 1.4 introduced it; this is why it exists.)
That second mode is what makes a command usable in a pipeline at all. A program that can only read a named file cannot be the second stage of anything.
Filename versus stream
The same command can produce slightly different output depending on which mode you use, which matters more than it sounds:
$ wc -l names.txt
3 names.txt
$ wc -l < names.txt
3Given a filename, wc prints it. Reading from stream one there is no name to print — wc has no idea where the data came from. Chapter 8.4 hit this when capturing a line count and getting the filename along with it; Chapter 12.4 covers the < operator properly.
6. Where the Streams Actually Point
The three streams are entries in a small per-process table, and on Linux you can look at it:
$ ls -l /proc/$$/fd
lrwx------ 1 engineer engineer 64 Oct 8 11:02 0 -> /dev/pts/3
lrwx------ 1 engineer engineer 64 Oct 8 11:02 1 -> /dev/pts/3
lrwx------ 1 engineer engineer 64 Oct 8 11:02 2 -> /dev/pts/3All three pointing at /dev/pts/3 — your terminal, which is a device file like any other. That is the literal meaning of "connected to your terminal".
Redirect something and an entry changes:
$ bash -c 'ls -l /proc/$$/fd' > /tmp/fds.txt
$ grep ' 1 ->' /tmp/fds.txt
l-wx------ 1 engineer engineer 64 Oct 8 11:02 1 -> /tmp/fds.txtEntry 1 now points at the file. Entries 0 and 2 still point at the terminal.
7. Inheritance
Streams are inherited, exactly like everything else in Chapter 7.4. That chapter listed "open files" among the things a child receives, and this is what it meant:
$ ./outer.sh > everything.logouter.sh, every command it runs, and every command those run all write into everything.log, because each inherited the redirected stream from its parent. One redirect covers an entire tree of processes.
This is why:
./regress.sh > run.log 2>&1captures the whole run, including output from tools three levels deep that know nothing about your redirect.- A background job keeps writing to your terminal (Chapter 7.5) — it inherited the terminal as its output and backgrounding does not change that.
- A detached job must be redirected (Chapter 7.8), because its inherited terminal is about to disappear.
Three behaviours from three different modules, one mechanism.
8. What the Rest of the Module Does
Everything that follows is this model plus an operator:
| Chapter | What it changes |
|---|---|
| 12.2 | Point stream two at a file |
| 12.3 | Point stream three somewhere, or at the same place as two |
| 12.4 | Point stream one at a file or at inline text |
| 12.5 | Connect one process's stream two to the next one's stream one |
| 12.6 | Send stream two to two places at once |
| 12.7 | Point a stream at nothing |
| 12.8 | Find out whether any of it worked |
Seven operators, one idea. If the table in §6 makes sense, the rest of the module is notation.
Practice
mkdir -p /tmp/streams && cd /tmp/streams
printf 'charlie\nalpha\nbravo\n' > names.txt1 — Split the two output streams.
ls /tmp /nope > out.txt 2> err.txt
echo "--- out ---"; head -3 out.txt
echo "--- err ---"; cat err.txt2 — Discard each in turn.
ls /tmp /nope > /dev/null
ls /tmp /nope 2> /dev/null | head -23 — Write to each stream from a script.
printf '#!/bin/bash\necho "this is a result"\necho "this is a problem" >&2\n' > both.sh
chmod +x both.sh
./both.sh
echo "--- with output redirected: ---"
./both.sh > /dev/null
echo "--- with errors redirected: ---"
./both.sh 2> /dev/null4 — Watch a command read from the keyboard.
wc -lType two lines, then press Ctrl-D.
5 — See the filename difference.
wc -l names.txt
wc -l < names.txt6 — Find out which stream a tool uses.
mytool() { echo "result line"; echo "progress: 50%" >&2; }
mytool > o.txt 2> e.txt
echo "stdout: [$(cat o.txt)]"
echo "stderr: [$(cat e.txt)]"Then do the same with a real tool you use.
7 — Look at the table, if you are on Linux.
ls -l /proc/$$/fd
bash -c 'ls -l /proc/$$/fd' > fds.txt; grep ' 1 ->' fds.txt8 — Prove inheritance.
printf '#!/bin/bash\necho "from the outer script"\n./both.sh\n' > outer.sh
chmod +x outer.sh
./outer.sh > everything.log 2>&1
cat everything.logOne redirect, output from two scripts.
9 — Clean up. cd /tmp && rm -rf /tmp/streams
Check Your Understanding
1. Why are there two output streams rather than one?
2. make > build.log and the errors still appear on your screen. Is something wrong?
3. A tool prints a progress percentage. Which stream should it use, and why is that not an error?
4. You run grep ERROR with no filename and nothing happens. What is it doing?
5. wc -l data.txt prints 42 data.txt but wc -l < data.txt prints 42. Why?
6. You redirect a script's output to a log and a tool it calls three levels down also ends up in the log. How?
9. Remember
- Every process starts with three open streams:
0input,1ordinary output,2error output. - All three point at your terminal by default, which is why output and errors look like one thing.
- They are separate, and that is the point. A pipe carries only stream two, so diagnostics stay visible.
- Stream two is for results — anything the next program might want. Stream three is for everything meant for a person: errors, warnings, progress, prompts.
- Progress output belongs on stream three and is not an error. The test is "would a pipeline want this?"
- Not every tool follows the convention. Run it with the two streams in two files and look.
- In a script, write diagnostics with
>&2or they vanish when somebody redirects your output. - A command with no filename reads stream one — your keyboard — and appears to hang.
Ctrl-Dends it. - Reading from a stream means there is no filename to print, which is why
wc -l < fdiffers fromwc -l f. - The streams are entries in a per-process table, which is why the operators use numbers —
2>means "point entry 2 somewhere else". - Streams are inherited, so one redirect covers a whole tree of processes.
- The rest of the module is seven operators applied to this one model.
- Which stream each of your own messages belongs on is Chapter 20.7 — and it decides whether your script can be used in a pipeline at all.
The first operator is the one everybody learns on day one and nobody is told the dangerous part of. The next chapter is > — including exactly when it destroys a file, and why it can do so even when the command never ran.
Where this fits
Part of the Linux & Bash curriculum.
