Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pipe stdout and stderr separately to two different processes while letting them appear in the terminal?

Tags:

bash

I have a process that generates output both on stderr and stdout.

I need to pipe these two different commands, but I would also like to keep seeing them on the terminal.

So I tried something like this as a proof of concept:

#!/usr/bin/env bash

set -e

function generate_output() {
  echo This message goes to stderr 1>&2
  echo This message goes to stdout
}

generate_output \
    1> >(tee <&0 >(cat > out.log)) \
    2> >(tee <&0 >(cat > err.log))

cat > out.log is a dummy command that will be replaced by something else when I figure out how to make this work.

It almost works :

$ cat err.log 
This message goes to stderr

And I see the output on the terminal.

So far so good !

But :

$ cat out.log 
This message goes to stdout
This message goes to stderr

Why does the "stderr" message ends up in out.log ?

What puzzles me even more is that if I remove the tee command, the log file contain the expected result (but then I loose terminal output)

#!/usr/bin/env bash

set -e

function generate_output() {
  echo This message goes to stderr 1>&2
  echo This message goes to stdout
}

generate_output \
    1> >(cat > out.log) \
    2> >(cat > err.log)
like image 802
Samuel Rossille Avatar asked Dec 30 '22 12:12

Samuel Rossille


1 Answers

Both tees are writing to stdout. That's fine for the first one but a problem for the second one since when the 2> redirection is processed 1> has already redirected stdout. That means that the second tee isn't writing to the terminal, but to the first tee process. Oops!

An elegant way to fix it is to have each of the tees write to the fd that they're reading from. Adding >&2 to the second one will fix the problem. And since I can tell you like parallel structure, you can add an explicit >&1 to the first one as well.

This will have the nice effect of preserving the separation of stdout and stderr in case there are any downstream consumers of your script's output.

generate_output \
    1> >(tee <&0 >(cat > out.log) >&1) \
    2> >(tee <&0 >(cat > err.log) >&2)

You can then eliminate some redundancies:

  • <&0 redirects stdin into stdin, which does nothing.
  • >(cat >file) is a complicated way of writing file. You can get the same effect just by passing out.log and err.log directly to tee.
generate_output \
    1> >(tee out.log >&1) \
    2> >(tee err.log >&2)
like image 149
John Kugelman Avatar answered Jan 21 '23 12:01

John Kugelman