I often want to do this, and find myself reaching for /dev/stderr
, but there can be problems with this approach; for example, Nix build scripts give "permission denied" errors if they try to write to /dev/stdout
or /dev/stderr
.
After reinventing this wheel a few times, my current approach is to use process substitution as follows:
myCmd 2> >(tee >(cat 1>&2))
Reading this from the outside in:
This will run myCmd
, leaving its stdout as-is. The 2>
will redirect the stderr of myCmd
to a different destination; the destination here is >(tee >(cat 1>&2))
which will cause it to be piped into the command tee >(cat 1>&2)
.
The tee
command duplicates its input (in this case, the stderr of myCmd
) to its stdout and to the given destination. The destination here is >(cat 1>&2)
, which will cause the data to be piped into the command cat 1>&2
.
The cat
command just passes its input straight to stdout. The 1>&2
redirects stdout to go to stderr.
Reading from the inside out:
The cat 1>&2
command redirects its stdin to stderr, so >(cat 1>&2)
acts like /dev/stderr
.
Hence tee >(cat 1>&2)
duplicates its stdin to both stdout and stderr, acting like tee /dev/stderr
.
We use 2> >(tee >(cat 1>&2))
to get 2 copies of stderr: one on stdout and one on stderr.
We can use the copy on stdout as normal, for example storing it in a variable. We can leave the copy on stderr to get printed to the terminal.
We can combine this with other redirections if we like, e.g.
# Create FD 3 that can be used so stdout still comes through
exec 3>&1
# Run the command, redirecting its stdout to the shell's stdout,
# duplicating its stderr and sending one copy to the shell's stderr
# and using the other to replace the command's stdout, which we then
# capture
{ ERROR=$( $@ 2> >(tee >(cat 1>&2)) 1>&3) ; }
echo "copy of stderr: $ERROR"