stdout and stderr go separately¶
Level: 101 · you have sent a command's output to a file and still seen its error on the screen
One line: > moves descriptor 1 and nothing else, so an error message still reaches the screen. 2>&1 makes descriptor 2 a copy of wherever descriptor 1 points at that moment, which is why > file 2>&1 puts both streams in the file and 2>&1 > file puts only stdout there. &> means "both" in bash 3.2, zsh and fish, and &>> is a syntax error in the bash a Mac ships.
Measured¶
The book's example is ls with one name that exists and one that does not. This page uses cat instead: GNU and BSD ls word the missing-file message differently, cat does not, and the idea is the same:
Verified output of redir_streams_sh.sh, identical on Linux and macOS — regenerated by tools/run_examples.py, never hand-typed.
$ echo one > exists
$ cat exists nosuch > out.txt; echo "status $?"
cat: nosuch: No such file or directory
status 1
$ cat out.txt
one
$ cat exists nosuch 2> err.txt; echo "--- err.txt:"; cat err.txt
one
--- err.txt:
cat: nosuch: No such file or directory
$ cat exists nosuch > out.txt 2> err.txt; echo "--- out.txt:"; cat out.txt; echo "--- err.txt:"; cat err.txt
--- out.txt:
one
--- err.txt:
cat: nosuch: No such file or directory
$ cat exists nosuch 2> /dev/null
one
$ cat exists nosuch > both.txt 2>&1; echo "--- both.txt:"; cat both.txt
--- both.txt:
one
cat: nosuch: No such file or directory
$ cat exists nosuch 2>&1 > both.txt; echo "--- both.txt:"; cat both.txt
cat: nosuch: No such file or directory
--- both.txt:
one
$ cat exists nosuch &> both.txt; echo "--- both.txt:"; cat both.txt
--- both.txt:
one
cat: nosuch: No such file or directory
$ cat exists nosuch >> both.txt 2>&1; echo "--- both.txt:"; cat both.txt
--- both.txt:
one
cat: nosuch: No such file or directory
one
cat: nosuch: No such file or directory
>is1>. The file gotone. The complaint aboutnosuchwent to descriptor 2, which still pointed where it always had. The status is 1 becausecatfailed on one of its two files.2>is a redirection of its own, and one of each on the same command splits the streams into two files.2> /dev/nullthrows the complaints away and keeps the answer.> both.txt 2>&1is read left to right. First descriptor 1 becomes the file, then descriptor 2 becomes a copy of descriptor 1, which is now the file. Both lines land in it, in the ordercatwrote them.2>&1 > both.txtdoes the same two steps the other way round. Descriptor 2 copies descriptor 1 while that is still the screen, then descriptor 1 moves to the file, and descriptor 2 stays behind.2>&1copies a destination; it does not tie the two descriptors together.&>gave the same file as> both.txt 2>&1, and>> both.txt 2>&1appended both streams.
On a Mac¶
The key above is shared: /bin/bash 3.2 printed the same bytes as bash 5.2 on Ubuntu. One operator is missing from 3.2, though. &>>, "append both", arrived in bash 4.0:
Verified output of redir_streams_bash4_sh.sh on Linux — regenerated by tools/run_examples.py, never hand-typed.
$ cat exists nosuch &>> both.txt
(exit status 1)
$ cat both.txt
one
cat: nosuch: No such file or directory
one
cat: nosuch: No such file or directory
$ cat exists nosuch >> both.txt 2>&1; wc -l < both.txt
6
Verified output of redir_streams_bash4_sh.sh on macOS — regenerated by tools/run_examples.py, never hand-typed.
$ cat exists nosuch &>> both.txt
bash: -c: line 0: syntax error near unexpected token `>'
bash: -c: line 0: `cat exists nosuch &>> both.txt'
(exit status 2)
$ cat both.txt
one
cat: nosuch: No such file or directory
$ cat exists nosuch >> both.txt 2>&1; wc -l < both.txt
4
On Linux the line appends, and cat's own status 1 comes back. On a Mac it is a syntax error, reported before anything runs, with status 2, and both.txt is untouched. So a #!/bin/bash script with &>> works on the Linux machine it was written on and fails on every Mac. The long spelling >> file 2>&1 works in both, as the last command shows.
In zsh and fish¶
zsh has every spelling above, including &>>, and adds >& as a third way to say "both":
Verified output of redir_streams_zsh.zsh, identical on Linux and macOS — regenerated by tools/run_examples.py, never hand-typed.
$ echo one > exists
$ cat exists nosuch > out.txt; echo "status $?"; echo "--- out.txt:"; cat out.txt
cat: nosuch: No such file or directory
status 1
--- out.txt:
one
$ cat exists nosuch > both.txt 2>&1; echo "--- both.txt:"; cat both.txt
--- both.txt:
one
cat: nosuch: No such file or directory
$ cat exists nosuch 2>&1 > both.txt; echo "--- both.txt:"; cat both.txt
cat: nosuch: No such file or directory
--- both.txt:
one
$ cat exists nosuch &> both.txt; echo "--- both.txt:"; cat both.txt
--- both.txt:
one
cat: nosuch: No such file or directory
$ cat exists nosuch &>> both.txt; echo "--- both.txt:"; cat both.txt
--- both.txt:
one
cat: nosuch: No such file or directory
one
cat: nosuch: No such file or directory
$ cat exists nosuch >& both.txt; echo "--- both.txt:"; cat both.txt
--- both.txt:
one
cat: nosuch: No such file or directory
The order rule is the same: 2>&1 > both.txt still left the error on the screen.
fish has 2>, 2>&1 with the same order rule, &> and &>>:
Verified output of redir_streams_fish.fish, identical on Linux and macOS — regenerated by tools/run_examples.py, never hand-typed.
$ echo one > exists
$ cat exists nosuch > out.txt; echo "status $status"; echo "--- out.txt:"; cat out.txt
cat: nosuch: No such file or directory
status 1
--- out.txt:
one
$ cat exists nosuch 2> /dev/null
one
$ cat exists nosuch > both.txt 2>&1; echo "--- both.txt:"; cat both.txt
--- both.txt:
one
cat: nosuch: No such file or directory
$ cat exists nosuch 2>&1 > both.txt; echo "--- both.txt:"; cat both.txt
cat: nosuch: No such file or directory
--- both.txt:
one
$ cat exists nosuch &> both.txt; echo "--- both.txt:"; cat both.txt
--- both.txt:
one
cat: nosuch: No such file or directory
$ cat exists nosuch &>> both.txt; echo "--- both.txt:"; cat both.txt
--- both.txt:
one
cat: nosuch: No such file or directory
one
cat: nosuch: No such file or directory
$ echo hi ^/dev/null
hi ^/dev/null
The last command matters if you learned fish a long time ago. Old fish releases used ^ for stderr. fish 4.3.2 treats ^/dev/null as an ordinary word and prints it, and nothing is redirected.
| bash | zsh | fish | |
|---|---|---|---|
| stderr to a file | 2> f |
2> f |
2> f |
| both to a file | > f 2>&1, &> f |
> f 2>&1, &> f, >& f |
> f 2>&1, &> f |
| append both | >> f 2>&1; &>> f from bash 4.0 |
&>> f |
&>> f |
2>&1 > f |
stderr stays on the screen | the same | the same |
If you are coming from another library¶
- Rust. Standard error, and exit status ↗ is the program's side of this page: what belongs on stdout, what belongs on stderr, and why the split matters to whoever redirects them.
- Encodings. A pipe is not a terminal ↗ — a program can tell whether descriptor 1 is a terminal, and some change what they print when you redirect them.
- Python. Standard in, standard out, and pipes ↗ outlines the same two streams as
sys.stdoutandsys.stderr.
See also¶
- Descriptors 0, 1 and 2 — the names
/dev/stdoutand/dev/stderr, and why>&2beats> /dev/stderr - Redirection truncates first — what
>does to the file before the command starts - tee saves and passes on —
2>&1 | tee build.log - bash manual, Redirections ↗
- zsh manual, Redirection ↗
- fish language, input and output redirection ↗