How to copy stderr without stopping writing to the terminal?

I want to write a shell script that runs a command, writing its stderr to my terminal as it arrives. However, I also want to keep stderr in a variable, so I can check it later.

How can i achieve this? Should I use tee , or a subshell, or something else?

I tried this:

 # Create FD 3 that can be used so stdout still comes through exec 3>&1 # Run the command, piping stdout to normal stdout, but saving stderr. { ERROR=$( $@ 2>&1 1>&3) ; } echo "copy of stderr: $ERROR" 

However, this does not write stderr to the console, but only saves it.

I also tried:

 { $@ ; } 2> >(tee stderr.txt >&2 ) echo "stderr was:" cat stderr.txt 

However, I do not need a temporary file.

+6
source share
2 answers

Credit owned by @ Ethan Reisner for the basics of the approach; however, it is better to use tee with /dev/stderr rather than /dev/tty in order to maintain normal behavior (if you send to /dev/tty , the outside world does not see it as a stderr output and can neither capture nor suppress it):

Here's the complete idiom:

 exec 3>&1 # Save original stdout in temp. fd #3. # Redirect stderr to *captured* stdout, send stdout to *saved* stdout, also send # captured stdout (and thus stderr) to original stderr. errOutput=$(" $@ " 2>&1 1>&3 | tee /dev/stderr) exec 3>&- # Close temp. fd. echo "copy of stderr: $errOutput" 
+2
source

I often want to do this and am for /dev/stderr , but there may be problems with this approach; for example, Nix build scripts give permission errors if they try to write to /dev/stdout or /dev/stderr .

After reusing this wheel several times, my current approach is to use a replacement process as follows:

 myCmd 2> >(tee >(cat 1>&2)) 

Reading this from the outside in:

This will work myCmd , leaving it stdout as it is. 2> redirects stderr myCmd to another destination; destination here >(tee >(cat 1>&2)) , which will lead to its forwarding to the tee >(cat 1>&2) command.

The tee command duplicates its input (in this case stderr myCmd ) to its stdout and to this destination. The destination is here >(cat 1>&2) , which will result in the transfer of data to the command cat 1>&2 .

The cat simply passes its input directly to standard output. 1>&2 redirects stdout to go to stderr.

Reading from the inside:

The cat 1>&2 command redirects its stdin to stderr, so >(cat 1>&2) acts like /dev/stderr .

Therefore, tee >(cat 1>&2) duplicates its stdin on 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 usual, for example, store it in a variable. We can leave a copy on stderr to print on the terminal.

We can combine this with other redirects if we like, for example

 # Create FD 3 that can be used so stdout still comes through exec 3>&1 # Run the command, redirecting its stdout to the shell stdout, # duplicating its stderr and sending one copy to the shell stderr # and using the other to replace the command stdout, which we then # capture { ERROR=$( $@ 2> >(tee >(cat 1>&2)) 1>&3) ; } echo "copy of stderr: $ERROR" 
+2
source

Source: https://habr.com/ru/post/978152/


All Articles