Bash

Redirection

To redirect both stdout and stderr to the same file, say afile, for an instance of a command, say cmd:

-> cmd 1>afile 2>&1

Equivalently:

-> cmd  >afile 2>&1

You can use operator '>>' to instead append output to afile without overwriting (i.e., truncating) it first:

-> cmd 1>>afile 2>&1
-> cmd  >>afile 2>&1

There are two shortcuts for simultaneously redirecting stdout and stderr to the same file, but they are both deprecated syntax:

-> cmd &>afile
-> cmd >&afile

The proscription applies to public scripts or otherwise written records; no boogeymen will call if you abbreviate interactively in the privacy of your own Bash session.

What you're really doing here is manipulating file descriptors 1 and 2, abbreviated henceforth as FD#1 and FD#2. You're not modifying stdout and stderr. Under the hood, it's these two file descriptors that get new file-system partners. The file streams stdout and stderr don't change: As usual and customary, stdout still calls on FD#1, and stderr still calls on FD#2.

The sinistrodextral order of expressions counts and makes all the difference. The first redirection above (1>afile) associates file descriptor 1, with file afile. Since output stream stdout uses FD#1, this expression redirects stdout to afile. The second redirection (2>&1) duplicates FD#1 as FD#2, and thus FD#2 binds to afile as a result. Since the output stream stderr uses FD#2, this expression redirects stderr to afile, too.

Despite its heritage as a duplicate, FD#2 is immediately emancipated as independent of FD#1; FD#2 is hence unaffected by subsequent changes to FD#1. Duplication is not aliasing. That's why reversing the order of the dual-redirection expression above would fail to associate stderr with afile. The following are equivalent:

-> cmd 2>&1 1>afile   # Not likely what you want
-> cmd 1>afile        # What you get

For example:

-> perl -e 'say stderr "Hi stderr"; say stdout "Hi stdout"' 1>afile 2>&1
-> cat afile 
Hi stderr
Hi stdout
-> perl -e 'say stderr "Hi stderr"; say stdout "Hi stdout"' 2>&1 1>afile2
Hi stderr
-> cat afile2 
Hi stdout

Both FD#1 and FD#2 serve the terminal initially (e.g., dev/pts/0), so duplicating FD#1 into FD#2 before redirecting FD#1 changes nothing for FD#2. Only stdout gets a new home.

You can use Bash command exec to redirect I/O for a sequence of commands, whether in a terminal or a script. This example redirects the output of two commands to file demo.txt and subsequently restores stdout to the terminal. First, note that FD#1 uses terminal /dev/pts/0 originally:

-> lsof -a -p $BASHPID -d 1
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
bash    10381  ray    1w   CHR  136,0      0t0    3 /dev/pts/0

Now redirect stdout to file demo.txt, run a pair of commands with output written there implicitly, and restore stdout:

-> exec 1>demo.txt
-> echo Delayed gratification--nothing echoed now.
-> lsof -a -p $BASHPID -d 1
-> exec 1>/dev/pts/0

The output from the echo and (second) lsof commands do not appear on the terminal. But file demo.txt tells their stories:

-> cat demo.txt
Delayed gratification--nothing echoed now.
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF   NODE NAME
bash    10381  ray    1w   REG   0,41      257 106464 /tmp/demo.txt

Note how the output of this lsof reveals whither FD#1 went during the escapade.

In general, you'll probably want to simply duplicate stdout before redirecting it with exec. When done, you can restore stdout to that duplicate. Something like this:

-> exec 3>&1 1>demo.txt
-> echo Delayed gratification--nothing echoed now.
-> lsof -a -p $BASHPID -d 1,3
-> exec 1>&3 3>&-
-> cat demo.txt
Delayed gratification--nothing echoed now.
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF   NODE NAME
bash    10381  ray    1w   REG   0,41      262 116273 /tmp/demo.txt
bash    10381  ray    3w   CHR  136,0      0t0      3 /dev/pts/0

That first redirection (3>&1) creates FD#3 and binds it to the same terminal that FD#1 resolves to—at the time of creation. FD#3 is an independent duplicate of FD#1, not merely and alias, and so the second redirection (1>demo.txt) has no effect on this new descriptor. Consequently, the third redirection (1>&3) restores stdout to its original terminal. That last redirection (3>&-) closes the extra file descriptor, just to be tidy:

-> lsof -a -p $BASHPID -d 1,3
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
bash    10381  ray    1w   CHR  136,0      0t0    3 /dev/pts/0

You can alternatively restore standard output and close its duplicate by moving FD#3 to FD#1 like so:

-> exec 1>&3-

FD#1 regains the saved file association from FD#3, and then FD#3 closes and returns to the pool of free descriptors.

You can safely use any free file descriptor from 3 through 9 for duplication. The Bash manual in section REDIRECTION advises care in going higher, lest you conflict with the shell's internal use of descriptors. Or you can just let Bash pick a free file descriptor for you and save it in a variable of your choosing, say $fd, for example:

-> exec {fd}>&1
-> echo $fd
10
-> exec 1>demo.txt
-> echo Delayed gratification--nothing echoed now.
-> lsof -a -p $BASHPID -d 1,$fd
-> exec 1>&$fd-
-> cat demo.txt
Delayed gratification--nothing echoed now.
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF   NODE NAME
bash    25643  ray    1w   REG   0,41      257 155498 /tmp/demo.txt
bash    25643  ray   10w   CHR  136,0      0t0      3 /dev/pts/0