Bash – How to analyze this command `{ 2>&3 “$@”& } 3>&2 2>/dev/null`

background-processbashio-redirectionshell

A few weeks ago, I have seen a weird answer about the question "(How to) silently start task(s) in the background?". This solution seems incorrect (c.f. my answer) although the shell seems to start the task silently in the background.

I. Issue: can we actually redirect the shell standard error?

There is no explanation with the proposed solution and an analyze does not provide a reliable answer.

Below, you can see the code snippet.

# Run the command given by "$@" in the background
silent_background() {
   if [[ -n $BASH_VERSION ]]; then
      { 2>&3 "$@"& } 3>&2 2>/dev/null
   fi
}

The problematic command is { 2>&3 "$@"& } 3>&2 2>/dev/null.

i) Analyze

The group of commands ({ ... }) specifies two redirections (3>&2 and 2>/dev/null) for one command (# Run the command given by "$@" in the background). The command is an asynchronous list (cmd&) having one redirection (2>&3).

POSIX specification

Each redirection shall apply to all the commands within the compound command that do not explicitly override that redirection.

The standard error redirection 2>/dev/null is overridden by the redirection associated to the asynchronous list 2>&3.

Concrete cases

In the first case, the standard error is redirected to /dev/null whereas, in the second case, the standard error of the command is still attached to the terminal.

prompt% { grep warning system.log& } 2>/dev/null
prompt% { 2>&3 grep warning system.log& } 3>&2 2>/dev/null
grep: system.log: No such file or directory

Below, we can see similar cases. In the first case, the standard output of the command is still attached to the terminal. In the second case, the standard output redirection of the command is modified: >echo.txt is overridden by >print.txt.

prompt% { >&3 echo some data...& } 3>&1 >echo.txt
[1] 3842
some data...
prompt% file echo.txt
echo.txt: empty
prompt% { >&3 echo some data...& } 3>print.txt >echo.txt
[1] 2765
prompt% file echo.txt
echo.txt: empty
prompt% cat print.txt
some data...

ii) Observations

As previously mentioned, the command seems to start silently in the background. More precisely, the notification about a background job, e.g. [1] 3842, is not displayed.

The previous analysis implies that the redirects are superfluous since they may be canceled.

{ redir_3 cmd& } redir_1 redir_2 is equivalent to cmd&.

iii) Interpretation

In my mind, the mentioned construct hides the notification because of a side effect.

Can you explain how that happens?

II. Hypothese(s)

Ilkkachu's answer and comments allowed some progression. Note that each process has its own file descriptors.

  • The shell messages may be send on the shell standard error.

Another context: An asynchronous list executed in a subshell. The command g does not exist.

prompt% ( g& )
g: command not found

Asynchronous commands, commands grouped with parentheses,…, are executed in a subshell environment that is a duplicate of the shell environment…

A subshell inherits the value of its standard error stream from its parent shell.

Therefore, in the previous case, their standard error streams should refer to the terminal. However, the job notification is not displayed whereas the shell error message is probably displayed on the shell standard error.

Best Answer

You missed the most important point, shell redirection is applied in order from left to right.

In:

{ 2>&3 "$@"& } 3>&2 2>/dev/null

The whole group command is run with:

  • File descriptor 3 => standard error, which is terminal at this time.
  • File descriptor 2 (Standard error) => /dev/null

So when command inside grouping run:

  • Standard error => File descriptor 3, which is pointed to terminal.

So if "$@"& print anything to its standard error, the output is printed to terminal.


For your concrete cases:

{ grep warning system.log& } 2>/dev/null

{ grep warning system.log& } runs with standard error is pointed to /dev/null. grep does not override any redirection so its standard error is the same with {...}, and is redirected to /dev/null, you got no output to terminal.

In:

{ 2>&3 grep warning system.log& } 3>&2 2>/dev/null

grep's standard error is redirected to file descriptor 3, which is pointed to terminal as explained above, so you got output to terminal.