Search code examples
linuxbashunixtee

How do I write standard error to a file while using "tee" with a pipe?


I know how to use tee to write the output (standard output) of aaa.sh to bbb.out, while still displaying it in the terminal:

./aaa.sh | tee bbb.out

How would I now also write standard error to a file named ccc.out, while still having it displayed?


Solution

  • I'm assuming you want to still see standard error and standard output on the terminal. You could go for Josh Kelley's answer, but I find keeping a tail around in the background which outputs your log file very hackish and cludgy. Notice how you need to keep an extra file descriptor and do cleanup afterward by killing it and technically should be doing that in a trap '...' EXIT.

    There is a better way to do this, and you've already discovered it: tee.

    Only, instead of just using it for your standard output, have a tee for standard output and one for standard error. How will you accomplish this? Process substitution and file redirection:

    command > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)
    

    Let's split it up and explain:

    > >(..)
    

    >(...) (process substitution) creates a FIFO and lets tee listen on it. Then, it uses > (file redirection) to redirect the standard output of command to the FIFO that your first tee is listening on.

    The same thing for the second:

    2> >(tee -a stderr.log >&2)
    

    We use process substitution again to make a tee process that reads from standard input and dumps it into stderr.log. tee outputs its input back on standard output, but since its input is our standard error, we want to redirect tee's standard output to our standard error again. Then we use file redirection to redirect command's standard error to the FIFO's input (tee's standard input).

    See Input And Output

    Process substitution is one of those really lovely things you get as a bonus of choosing Bash as your shell as opposed to sh (POSIX or Bourne).


    In sh, you'd have to do things manually:

    out="${TMPDIR:-/tmp}/out.$$" err="${TMPDIR:-/tmp}/err.$$"
    mkfifo "$out" "$err"
    trap 'rm "$out" "$err"' EXIT
    tee -a stdout.log < "$out" &
    tee -a stderr.log < "$err" >&2 &
    command >"$out" 2>"$err"