Redirection failing if |& is used instead of | (pipe) - bash bug?
tl;dr
Not a bug. |&
is like an alias for 2>&1 |
, a shorthand, a way to type less. Note 2>&1
is now the final redirection in this part of the pipeline. That's all.
Analysis
The following citation is quite clear to me. You need to read the whole of it. In the question you cited few sentences without the broader context.
The output of each command in the pipeline is connected via a pipe to the input of the next command. That is, each command reads the previous command’s output. This connection is performed before any redirections specified by the command.
If
|&
is used, command1’s standard error, in addition to its standard output, is connected to command2’s standard input through the pipe; it is shorthand for2>&1 |
. This implicit redirection of the standard error to the standard output is performed after any redirections specified by the command.
(emphasis mine, source link)
Take foo … |& bar
, where …
denotes redirections specified by the command. |&
is a shorthand (think: "alias") for 2>&1 |
, so the command is like foo … 2>&1 | bar
; and the foo
part is like
foo >fifo … 2>&1
where >fifo
denotes the connection to the stdin of bar
(I used the name fifo
but in fact it's an anonymous pipe).
There is no deeper philosophy, |&
simply "expands" to 2>&1 |
. Then |
behaves like >fifo
before …
("this connection is performed before any redirections specified by the command") and 2>&1
stays at the end ("this implicit redirection of the standard error to the standard output is performed after any redirections specified by the command"). In general redirections are performed from left to right, so >fifo … 2>&1
is a good representation of what happens.
Example: your command
Your command:
strace echo something 2>&1 >/dev/null |& wc -l
"expands" to:
strace echo something 2>&1 >/dev/null 2>&1 | wc -l
and the strace
part behaves like:
strace echo something >fifo 2>&1 >/dev/null 2>&1
where >/dev/null 2>&1
redirects the stdout to /dev/null
and then stderr to what stdout is now, so also to /dev/null
. It doesn't matter at all where the stdout and stderr pointed to just before >/dev/null 2>&1
; this particular snippet does not depend on previous redirections.
Example: your workaround
Your workaround is:
(strace echo something 2>&1 >/dev/null) |& wc -l
which "expands" to:
(strace echo something 2>&1 >/dev/null) 2>&1 | wc -l
and the first part is like:
(strace echo something 2>&1 >/dev/null) >fifo 2>&1
Note it's different than >fifo 2>&1 >/dev/null 2>&1
we observed in the allegedly failing case.
For completeness let's analyze how (foo 2>&1 >/dev/null) >fifo 2>&1
works:
-
First a subshell is created, its stdin gets redirected to the fifo (
>fifo
), its stderr gets redirected to the current stdout (the last2>&1
), so to the fifo as well. -
Then
foo
is about to run inside the subshell. Without further redirections it would inherit the standard descriptors from the subshell. This means the "default" stdout and stderr offoo
both go to the fifo. -
Stderr of
foo
gets redirected to its current stdout (the first2>&1
). They are both the fifo, so this changes nothing. Then stdout offoo
gets redirected to/dev/null
(>/dev/null
).
In effect stderr (but not stdout) of foo
goes to the fifo, i.e. through the pipe.
Conclusions
The whole issue is not "redirection failing", it's not a bug. It's simply because |&
turns into 2>&1 |
, but then |
acts before and 2>&1
acts after any explicit redirections you specify.
Do not consider |&
as "|
with extra functionality". When using it, think of an alias, a textual replacement: imagine |&
will expand to 2>&1 |
(as if you typed 2>&1 |
instead of |&
); then 2>&1
and |
will do their respective jobs independently (as if |&
was never a thing).