lucasem
lucasem

Reputation: 491

piping stderr and stdout separately

I'd like to do different things to the stdout and stderr of a particular command. Something like

cmd |1 stdout_1 | stdout_2 |2 stderr_1 | stderr_2

where stdout_x is a command specifically for stdout and stderr_x is specifically for stderr. It's okay if stderr from every command gets piped into my stderr commands, but it's even better if the stderr could be strictly from cmd. I've been searching for some syntax that may support this, but I can't seem to find anything.

Upvotes: 5

Views: 855

Answers (4)

Simon Sobisch
Simon Sobisch

Reputation: 7288

Late-answer - as with all previous answers stderr is landing in stdout at the end (@anubhava's answer was nearly complete).

To pipe stderr and stdout independently (or identical) and keep them in stdout and stderr, we can use file descriptors.

Solution:

{ { cmd | stdout_pipe ; } 2>&1 1>&3 | stderr_pipe; } 1>&2 3>&1

Explanation:

  • piping in the shell always happens on stdout (file descriptor 1)
  • we therefore apply the pipe for stdout directly (leaving stderr intact)
  • then we park stdout in a temporary file descriptor and move stderr to stdout, allowing us to pipe this in the next step
  • at the end we get the "current" stdout (piped stderr) back to stderr and stdout back from our temporary file descriptor

Example:

using

cmd = { echo 'out'; echo >&2 'error'; }
stdout_pipe = awk '{print "stdout: " $0}'
stderr_pipe = awk '{print "stderr: " $0}'

{ { { echo 'out'; echo >&2 'error'; } \
  | awk '{print "stdout: " $0}'; } 2>&1 1>&3 \
  | awk '{print "stderr: " $0}'; } 1>&2 3>&1

Note: this example has an extra pair of { } to "combine" both echo commands into a single one

You see the difference when redirecting the output of a script using this or when using this with a terminal that colors stderr.

Upvotes: 0

anubhava
anubhava

Reputation: 784868

You can make use of a different file descriptor:

{ cmd 2>&3 | stdout_1; } 3>&1 1>&2 | stderr_1

Example:

{ { echo 'out'; echo >&2 'error'; } 2>&3 | awk '{print "stdout: " $0}'; } 3>&1 1>&2 |
  awk '{print "stderr: " $0}'
stderr: error
stdout: out

Or else use process substitution:

cmd 2> >(stderr_1) > >(stdout_1)

Example:

{ echo 'out'; echo >&2 'error'; } 2> >(awk '{print "stderr: " $0}') \
> >(awk '{print "stdout: " $0}')
stderr: error
stdout: out

to pipe stdout and stderr separately from your cmd.

Upvotes: 4

Mark Reed
Mark Reed

Reputation: 95242

The most straightforward solution would be something like this:

(cmd | gets_stdout) 2>&1 | gets_stderr

The main drawback being that if gets_stdout itself has any output on stdout, that will also go to gets_stderr. If that is a problem, you should use one of anubhava's or Kevin's answers.

Upvotes: 0

Kevin
Kevin

Reputation: 56049

You can use process substitution and redirection to achieve this:

cmd 2> >(stderr_1 | stderr_2) | stdout_1 | stdout_2

Upvotes: 3

Related Questions