pipe stdout and stderr to two different processes in shell script?
I've a pipline doing just
command1 | command2
So, stdout of command1 goes to command2 , while stderr of command1 go to the terminal (or wherever stdout of the shell is).
How can I pipe stderr of command1 to a third process (command3
) while stdout is still going to command2 ?
Solution 1:
Use another file descriptor
{ command1 2>&3 | command2; } 3>&1 1>&2 | command3
You can use up to 7 other file descriptors: from 3 to 9.
If you want more explanation, please ask, I can explain ;-)
Test
{ { echo a; echo >&2 b; } 2>&3 | sed >&2 's/$/1/'; } 3>&1 1>&2 | sed 's/$/2/'
output:
b2
a1
Example
Produce two log files:
1. stderr
only
2. stderr
and stdout
{ { { command 2>&1 1>&3; } | tee err-only.log; } 3>&1; } > err-and-stdout.log
If command
is echo "stdout"; echo "stderr" >&2
then we can test it like that:
$ { { { echo out>&3;echo err>&1;}| tee err-only.log;} 3>&1;} > err-and-stdout.log
$ head err-only.log err-and-stdout.log
==> err-only.log <==
err
==> err-and-stdout.log <==
out
err
Solution 2:
The accepted answer results in the reversing of stdout
and stderr
. Here's a method that preserves them (since Googling on that purpose brings up this post):
{ command 2>&1 1>&3 3>&- | stderr_command; } 3>&1 1>&2 | stdout_command
Notice:
-
3>&-
is required to prevent fd 3 from being inherited bycommand
. (As this can lead to unexpected results depending on whatcommand
does inside.)
Parts explained:
-
Outer part first:
-
3>&1
-- fd 3 for{ ... }
is set to what fd 1 was (i.e.stdout
) -
1>&2
-- fd 1 for{ ... }
is set to what fd 2 was (i.e.stderr
) -
| stdout_command
-- fd 1 (wasstdout
) is piped throughstdout_command
-
-
Inner part inherits file descriptors from the outer part:
-
2>&1
-- fd 2 forcommand
is set to what fd 1 was (i.e.stderr
as per outer part) -
1>&3
-- fd 1 forcommand
is set to what fd 3 was (i.e.stdout
as per outer part) -
3>&-
-- fd 3 forcommand
is set to nothing (i.e. closed) -
| stderr_command
-- fd 1 (wasstderr
) is piped throughstderr_command
-
Example:
foo() {
echo a
echo b >&2
echo c
echo d >&2
}
{ foo 2>&1 1>&3 3>&- | sed -u 's/^/err: /'; } 3>&1 1>&2 | sed -u 's/^/out: /'
Output:
out: a
err: b
err: d
out: c
(Order of a -> c
and b -> d
will always be indeterminate because there's no form of synchronization between stderr_command
and stdout_command
.)