Issue
There's an example explaining how the order of redirect command will affect the final result in the manual of bash
as
Note that the order of redirections is significant. For example, the command
ls > dirlist 2>&1
directs both standard output (file descriptor 1) and standard error (file descriptor 2) to the file dirlist, while the command
ls 2>&1 > dirlist
directs only the standard output to file dirlist, because the standard error was made a copy of the standard output before the standard output was redirected to dirlist.
In the second case, if my understanding is correct, 2>&1
means everything in stderr
will be redirected to stdout
and > dirlist
means everything in stdout
will be redirected to file dirlist
. The final result is that only stderr
is shown in the console.
My question is that does such a phenomenon means the redirect of bash is not hierarchical? Since if it's hierarchical, the stderr
has already been put into stdout
and should be put into the file dirlist
together with stdout
.
Solution
You need to see ">&n" (iow, "1>&n") as "fd 1 is redirected to wherever fd n is currently going to", not "fd 1 will go to wherever fd n is going even if fd n changes after that". Thus I dislike the term "copy" as it is to me a bit ambiguous: 'n>&m' make fd n 'point' to wherever fd m currently points to (and does not link fd n to fd m in any way, ie it does not make them "the same fd". they both still ar different fd's, that can change independently)
ex1 : in an interactive shell, where stdout & stderr both go to your terminal: Every commands you type in this shell will output their STDOUT (fd 1) to the terminal as well, and their STDERR (fd 2) to the terminal as well, by default (unless you tell them to output to something else)
So when you redirect a command to output somewhere else:
command 2>&1 >/dev/null
# now `command`'s STDERR (its `fd 2`) goes to: the terminal (again), as by default its `fd 1` goes to terminal
# and then only its `fd 1` (its stdout) is redirected to /dev/null, discarding it.
command >/dev/null 2>&1
# here, fd 1 (stdout) is redirected to /dev/null
# and then fd 2 (stderr) goes to where fd 1 is *currently* going, ie /dev/null too
most commands with a '>&something' or '>something' have this redirection be temporary. A special case is 'exec >something' or 'exec >&something', which changes the redirection of the current shell or program, and thus stays in effect "until another exec changes it again" or until the current shell or program finishes. iow, 'exec someredirection' affects not a subcommand, but the current program/shell.
ex2 : in a program launched with : program >/some/log_stdout 2>/some/log_stderr
# beginning of the program
... # anything here will have its stdout go to /some/log_stdout and stderr go to /some/log_stderr
exec 3>&1 # cree a new fd, 3, that goes to where stdout currently goes (/some/log_stdout)
exec 4>&2 # cree a new fd, 4, that goes to where stderr currently goes (/some/log_stderr)
exec 1>/dev/null # redirect fd 1 to /dev/null. fd 3 still goes to /some/log_stdout
exec 2>/dev/null # redirect fd 2 to /dev/null. fd 4 still goes to /some/log_stderr
something # here, no output: this command "something" outputs to fd1 and fd2, both going to /dev/null
other thing # same for that one
exec 5>&1 # this new fd, 5, goes to where fd1 currently goes (/dev/null)
somecommand ... >&3
# here, the STDOUT (only) of somecommand goes to where fd 3 currently points to,
# ie to /some/log_stdout (and of course just for the durection of somecommand)
another_command
# no output, as it outputs by default its stdout to fd 1 and stderr to fd 2, both going to /dev/null
# etc.
exec 1>&3 # fd 1 now goes to wherever fd3 was going,
# and thus we "restore" the original fd 1
# (as no 'exec 3>...' where done to change fd 3)
exec 2>&4 # same thing for fd 2, going to where fd 4 is going
# (which was where fd 2 was originally going)
It may help to "imagine" the following table attached to your command (or your shell, or your script:
your interactive shell usually will have this by default:
fd 1(=stdout) _goes_to_ your terminal
fd 2(=stderr) _goes_to_ your terminal
Each command it launches will also have 'a copy of this table' (or rather, their process space inherit the launching command/shell's current fd's), unless told otherwise.
When you launch : somecommand >/dev/null 2>&1
, before somecommand is launched, fd 1 is first pointed to "/dev/null", and fd 2 is then pointed to where fd 1 now points (/dev/null) and the process is launched with this 'table':
fd 1(=stdout) _goes_to_ /dev/null
fd 2(=stderr) _goes_to_ /dev/null
The shell that launched the command still have its own table, unchanged. (unless 'somecommand' was in fact 'exec n>somewhere', which changes the current shell's process fd n to point to 'somewhere', "permanently" (well, until the current shell finishes.).
Answered By - Olivier Dulac Answer Checked By - Candace Johnson (WPSolving Volunteer)