I was doing some testing on bash on my understanding of file descriptor redirection and discovered that:
((echo STDOUT; echo STDERR >&2;) 3>&1 1>&2 2>&3 3>&-) 2> /dev/null
and
(echo STDOUT; echo STDERR >&2;) 3>&1 1>&2 2>&3 3>&- 2> /dev/null
gave different results. The first giving the expected output of STDERR
since the stdout
and stderr
fds were swapped, but the second giving an unexpected output of STDOUT
as if the fds were never swapped in the first place.
Which to me is surprising. In this context, I don't see what the extra parenetical would be doing to change the output. Using braces has the same effect.
EDIT
So what I thought was "expected" results was actually wrong as I was visualizing incorrectly what was happening. What is actually happening is (with the left side of the -> being the file descriptor (or in the case of the last column, the command) and the right side being to what the underlying file handle will be output to):
1 -> stdout 1 -. ,-> stdout 1 -. ,-> /dev/null echo STDOUT -> stderr
X X
2 -> stderr 2 -' '-> stderr 2 -' '-> stderr echo STDERR >&2 -> /dev/null
With @JohnKugelman explanation, this now makes sense.
1 -> stdout 1 -> stdout 1 -. ,-> stdout echo STDOUT -> /dev/null
X
2 -> stderr 2 -> /dev/null 2 -' '-> /dev/null echo STDERR >&2 -> stdout
CodePudding user response:
Redirections are processed first, even if they're written after a command. This fact is crucial. It means 2> /dev/null
isn't performed last.
Although 2> /dev/null
is at the end of the line it's actually executed before the subshell starts. (subshell) 2>/dev/null
is actually 2>/dev/null (subshell)
. That means it executes before the descriptors are swapped in the first example, but afterwards in the second.
Let's take a deep dive.
With outer subshell
((echo STDOUT; echo STDERR >&2;) 3>&1 1>&2 2>&3 3>&-) 2> /dev/null
This is equivalent to:
2>/dev/null (3>&1 1>&2 2>&3 3>&- (echo STDOUT; >&2 echo STDERR))
The steps are:
2> /dev/null
: fd 2 is redirected to/dev/null
.- The outer subshell starts.
3>&1 1>&2 2>&3 3>&-
: fds 1 and 2 are swapped.- The inner subshell starts.
echo STDOUT
: Writes to fd 1, which is/dev/null
.>&2
: fd 1 is redirected to fd 2, which is the stdout of the original shell.echo STDERR
: Writes to fd 1, which is stdout.
Without outer subshell
(echo STDOUT; echo STDERR >&2;) 3>&1 1>&2 2>&3 3>&- 2> /dev/null
This is equivalent to:
3>&1 1>&2 2>&3 3>&- 2>/dev/null (echo STDOUT; >&2 echo STDERR))
The steps are:
3>&1 1>&2 2>&3 3>&-
: fds 1 and 2 are swapped.2>/dev/null
: fd 2 is redirected to/dev/null
.- The subshell starts.
echo STDOUT
: Writes to fd 1, which is stderr of the original shell.>&2
: fd 1 is redirected to fd 2, which is/dev/null
.echo STDERR
: Writes to fd 1, which is/dev/null
.