Skip to content

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
  • > is 1>. The file got one. The complaint about nosuch went to descriptor 2, which still pointed where it always had. The status is 1 because cat failed 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/null throws the complaints away and keeps the answer.
  • > both.txt 2>&1 is 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 order cat wrote them.
  • 2>&1 > both.txt does 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>&1 copies a destination; it does not tie the two descriptors together.
  • &> gave the same file as > both.txt 2>&1, and >> both.txt 2>&1 appended 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

See also