Search code examples
bashstderr

How do I copy stderr without stopping it 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 save stderr to a variable, so I can inspect it later.

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

I've 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 doesn't write stderr to the console, it only saves it.

I've also tried:

{ $@; } 2> >(tee stderr.txt >&2 )

echo "stderr was:"
cat stderr.txt

However, I don't want the temporary file.


Solution

  • Credit goes to @Etan Reisner for the fundamentals of the approach; however, it's better to use tee with /dev/stderr rather than /dev/tty in order to preserve normal behavior (if you send to /dev/tty, the outside world doesn't see it as stderr output, and can neither capture nor suppress it):

    Here's the full 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"