Home > Net >  What is the extra set of () or {} doing to change what gets redirected to /dev/null?
What is the extra set of () or {} doing to change what gets redirected to /dev/null?

Time:11-30

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:

  1. 2> /dev/null: fd 2 is redirected to /dev/null.
  2. The outer subshell starts.
  3. 3>&1 1>&2 2>&3 3>&-: fds 1 and 2 are swapped.
  4. The inner subshell starts.
  5. echo STDOUT: Writes to fd 1, which is /dev/null.
  6. >&2: fd 1 is redirected to fd 2, which is the stdout of the original shell.
  7. 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:

  1. 3>&1 1>&2 2>&3 3>&-: fds 1 and 2 are swapped.
  2. 2>/dev/null: fd 2 is redirected to /dev/null.
  3. The subshell starts.
  4. echo STDOUT: Writes to fd 1, which is stderr of the original shell.
  5. >&2: fd 1 is redirected to fd 2, which is /dev/null.
  6. echo STDERR: Writes to fd 1, which is /dev/null.
  • Related