Why does 2>&1 need to come before a | (pipe) but after a "> myfile" (redirect to file)?

bash, pipe, pipeline, shell, stdout

Solution

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.

cmd 2>&1 | less

First stdout of the first subshell is redirected to the pipe from which `less` is reading. Next, the `2>&1` redirect is applied to the first command. Redirecting stderr to stdout works because stdout is already pointing at the pipe.

cmd | less 2>&1

Here, the redirect applies to `less`. Less's stdout and stderr both presumably started out pointed at the terminal, so `2>&1` in 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):

{ { cmd1 >&3; cmd2; } 2>&1 | cmd3; } 3>&2

Might be a typical example. The end result is: `cmd1` and `cmd2`'s stderr -> `cmd3`; `cmd2`'s stdout -> `cmd3`; and `cmd1` and `cmd3`'s stderr, and `cmd3`'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:

f() { echo out; echo err >&2; }; f >/dev/null |& cat

Now, counterintuitively, all output is hidden. First stdout of `f` goes to the pipe, next stdout of `f` is redirected to `/dev/null`, and finally, stderr is redirected to stdout (`/dev/null` still).

I recommend never using `|&` in Bash -- it's used here for demonstration.

- Many of the obligatory redirection links

Problem

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?

Original source