Somebody at work recently asked if it were possible in Bash to have stdout and stderr both visible on the terminal but also copied to separate log files.

Normally when I think of console output and file logging at the same time, I think of "tee". But it can only handle stdout.

More recently another (the same?) coworker linked to http://www.catonmat.net/blog/bash-one-liners-explained-part-three/ which among other things contains this gem:

  $ command > >(stdout_cmd) 2> >(stderr_cmd)

That pipes stdout to the first command and stderr to the second.

Aha. So here's the answer to our original problem:

  $ command >  >(tee /tmp/stdout.log) 2> >(tee /tmp/stderr.log 1>&2)

Here's a quick test. First we'll a command that creates both stdout and stderr:

  $ function blather() {
  $  echo "hi stdout"
  $  echo "hi stderr" 1>&2
  $ }

  $ blather
  hi stdout
  hi stderr
  $ blather > /dev/null
  hi stderr
  $ blather 2> /dev/null 
  hi stdout

Now with logs for both stdout and stderr:

  $ blather > >(tee /tmp/stdout.log) 2> >(tee /tmp/stderr.log 2>&1)
  hi stdout
  hi stderr
  $ cat /tmp/stdout.log
  hi stderr
  $ cat /tmp/stderr.log
  hi stdout

A bit unwieldy. Maybe we can wrap this up in a function?

  function logdor() {
    stdout_file=$1
    shift
    stderr_file=$1
    shift
    "$@"  > >(tee -a $stdout_file) 2> >(tee -a $stderr_file 1>&2)
  }

(Note the use of tee -a to append rather than overwrite the log files.)

This basically works, although the console output appears after the prompt:

pw@Pauls-MacBook-Air ~ $ logdor /tmp/to1 /tmp/to2 echo hello this is stdout
pw@Pauls-MacBook-Air ~ $ hello this is stdout

pw@Pauls-MacBook-Air ~ $ cat /tmp/to1 
hello this is stdout
pw@Pauls-MacBook-Air ~ $ cat /tmp/to2 
pw@Pauls-MacBook-Air ~ $ logdor /tmp/to1 /tmp/to2 cat /no/such/file
cat: /no/such/file: No such file or directory
pw@Pauls-MacBook-Air ~ $ cat /tmp/to1 
hello this is stdout
pw@Pauls-MacBook-Air ~ $ cat /tmp/to2 
cat: /no/such/file: No such file or directory

Some other limitations: the command argument can't be a subshell like (foo; bar). It can however be a function:

$ logdor /tmp/to1 /tmp/to2 blather
hi stdout
hi stderr

$ cat /tmp/to1
hello this is stdout
hi stdout

$ cat /tmp/to2
hello this is stderr
hi stderr

Comments

comments powered by Disqus