When combining stderr with stdout, why does 2>&1 need to come before a | (pipe) but after a > myfile (redirect to file)?
To redirect stderr to stdout for file output:
echo > myfile 2>&1
To redirect stderr to stdout for a pipe:
echo 2>&1 | less
My assumption was that I could just do:
echo | less 2>&1
and it would work, but it doesn’t. Why not?
A pipeline is a |-delimited list of commands. Any redirections you specify apply to the constituent commands (simple or compound), but not to the pipeline as a whole. Each pipe chains one command’s stdout to the stdin of the next by implicitly applying a redirect to each subshell before any redirects associated with a command are evaluated.
First stdout of the first subshell is redirected to the pipe from which
lessis reading. Next, the2>&1redirect is applied to the first command. Redirecting stderr to stdout works because stdout is already pointing at the pipe.Here, the redirect applies to
less. Less’s stdout and stderr both presumably started out pointed at the terminal, so2>&1in this case has no effect.If you want a redirect to apply to an entire pipeline, to group multiple commands as part of a pipeline, or to nest pipelines, then use a command group (or any other compound command):
Might be a typical example. The end result is:
cmd1andcmd2‘s stderr ->cmd3;cmd2‘s stdout ->cmd3; andcmd1andcmd3‘s stderr, andcmd3‘s stdout -> the terminal.If you use the Bash-specific
|&pipe, things get stranger, because each of the pipeline’s stdout redirects still occur first, but the stderr redirect actually comes last. So for example:Now, counterintuitively, all output is hidden. First stdout of
fgoes to the pipe, next stdout offis redirected to/dev/null, and finally, stderr is redirected to stdout (/dev/nullstill).I recommend never using
|&in Bash — it’s used here for demonstration.