Redirection and Pipes · about 40 min

Standard error, /dev/null and ordering

Why this matters

Imagine a script that produces a report and saves it with > report.txt. One input file is missing. If the error message went into the report, the report would be corrupted and nobody would see the problem. Unix avoids this by giving every program two output channels: one for results, one for complaints. Knowing how to steer each of them separately, merge them, or throw one away is something you will do every day, and the syntax (2>&1) is famously confusing until you see what it does to the file descriptor table.

Two outputs, on purpose

From the previous lesson: fd 1 is standard output, fd 2 is standard error. Both start out pointing at the terminal, so you see them mixed. But they are separate descriptors, and > only moves fd 1:

$ ls /etc/hostname /nope > out.txt
ls: cannot access '/nope': No such file or directory
$ cat out.txt
/etc/hostname

The listing went to the file; the error stayed on the screen, where a human would notice it. That is the design: results on stdout, diagnostics on stderr. It also means a pipeline (next lesson) passes only real data to the next program, while errors still reach you.

Redirecting by number

Every redirection operator can take a file descriptor number in front of it:

SyntaxMeaning
> fsame as 1> f: stdout to file f (truncate)
2> fstderr to file f (truncate)
2>> fstderr appended to f
< fsame as 0< f: stdin from file f
2>&1make fd 2 a copy of fd 1 (as it is at this point)
>&2same as 1>&2: make fd 1 a copy of fd 2
&> fbash shorthand: both stdout and stderr to f

No space between the number and the operator: 2> is one token, while 2 > means "the argument 2, then >".

$ ls /etc/hostname /nope > out.txt 2> err.txt
$ cat out.txt
/etc/hostname
$ cat err.txt
ls: cannot access '/nope': No such file or directory

N>&M means: make fd N refer to whatever fd M refers to right now. Underneath, bash calls the dup2(M, N) system call. It copies the table entry; it does not create a lasting link between the two numbers.

Watch what happens to the table, step by step, for ls /etc /nope > all.txt 2>&1:

                       fd 1        fd 2
  start                terminal    terminal
  > all.txt            all.txt     terminal
  2>&1                 all.txt     all.txt      (copy of fd 1, which is the file)
  → both streams go into all.txt

Now the other order, ls /etc /nope 2>&1 > all.txt:

                       fd 1        fd 2
  start                terminal    terminal
  2>&1                 terminal    terminal     (copy of fd 1, which is still the terminal)
  > all.txt            all.txt     terminal
  → only stdout goes into the file; errors still on screen

Redirections are processed left to right, like assignments. "> file 2>&1" reads as "send stdout to the file, then send stderr wherever stdout goes now". Say it like that and you will never get the order wrong.

bash also has &> file (and &>> file to append) which does both at once. It is convenient interactively, but it is not part of the POSIX standard and fails in sh scripts, so prefer > file 2>&1 in scripts (module 16 covers portability).

When both fds share one open file, the writes land in the order the program makes them. One subtlety: many programs buffer standard output when it is not a terminal (collecting it and writing in big chunks) but write standard error immediately. So the interleaving in a merged file can differ from what you saw on screen. Shell builtins like echo write immediately, so scripts made of echos keep their order.

Writing to standard error yourself

Your own scripts should follow the convention too: errors and warnings to stderr. echo always writes to fd 1, so redirect that one command:

echo "error: config file not found" >&2

>&2 makes fd 1 of this echo a copy of fd 2, so its output goes wherever the shell's stderr goes. If someone runs your script with > report.txt, the error still reaches their terminal. You will use this pattern in nearly every script from module 8 on.

/dev/null: the bottomless file

/dev/null is a special device file. Anything written to it is accepted and discarded; reading from it gives end-of-file immediately.

$ ls -l /dev/null
crw-rw-rw- 1 root root 1, 3 Nov  1 14:00 /dev/null

The c at the start means character device: not data on disk, but a door into a kernel driver. The driver for "1, 3" simply throws bytes away. It is writable by everyone.

Common uses:

grep -r TODO . 2>/dev/null          # hide "Permission denied" noise, keep results
command -v jq > /dev/null 2>&1      # only care about the exit status
noisy_tool > /dev/null              # keep the errors, drop the chatter
sort < /dev/null                    # run with empty input

The second line is the classic "is this program installed?" test: all output discarded, and the exit status tells the answer.

Warning: Be careful about hiding stderr. 2>/dev/null makes a failing command look silent. Discard errors only when you know exactly which messages you are hiding and why.

Common mistakes

Exercises

The quiz is about order. The two tasks use a small program, noisy.sh, that writes to both streams; the code exercise has you write to stderr yourself.

Recap

Going further: try ls /etc /nope 3>&1 1>&2 2>&3 and work out on paper what each step does to the table (this swaps stdout and stderr). Then ls -l /proc/self/fd 2>/dev/null and see what fd 2 points to.

Exercises

Exercise 1 · quiz

Which order?

/etc exists and /nope does not. Where does the error message about /nope end up with this command?

ls /etc /nope 2>&1 > out.txt

Answer
  • On the terminal; only the listing of /etc goes to out.txt.

Redirections are applied left to right, and 2>&1 means "make fd 2 a copy of whatever fd 1 is right now". At that moment fd 1 is still the terminal, so fd 2 becomes the terminal (it already was). Only then does > out.txt move fd 1 to the file. fd 2 keeps pointing at the terminal. To send both to the file, write > out.txt 2>&1: first move fd 1, then copy it.

Exercise 2 · task in a shell

Separate the streams

Open the shell. /work/noisy.sh is a small program that writes some lines to standard output and some to standard error. Run it with ./noisy.sh and look: on the terminal they are mixed together.

Now run it again so that

with nothing appearing on the terminal.

Hint 1

> alone means 1>. Standard error is file descriptor 2.

Hint 2

./noisy.sh > out.txt 2> err.txt

Reference solution
./noisy.sh > out.txt 2> err.txt

Exercise 3 · task in a shell

Both into one file, in order

Now run ./noisy.sh once more so that both streams go into a single file /work/all.txt, in the same order as they appeared on the terminal (five lines).

Use the portable form with 2>&1, and mind the order of the two redirections.

Hint 1

2>&1 copies whatever fd 1 points to at that moment.

Hint 2

First move fd 1 to the file, then copy it into fd 2: ./noisy.sh > all.txt 2>&1

Reference solution
./noisy.sh > all.txt 2>&1

Exercise 4 · write code

Errors go to standard error

Write a script that prints

The checker runs bash script.sh > out 2> err and checks that each line went to the right stream. Use echo and the >&2 redirection for the warning.

File: script.sh

Starting point

echo "Processing 3 files"
echo "warning: disk almost full"
Hint 1

>&2 is short for 1>&2: make fd 1 of this one command a copy of fd 2.

Hint 2

echo "warning: disk almost full" >&2

Reference solution
echo "Processing 3 files"
echo "warning: disk almost full" >&2