tee and exit status


is there an alternative to “tee” which captures STDOUT/STDERR of the command being executed and exits with the same exit status as the processed command. Something as following:

eet -a some.log -- mycommand --foo --bar

Where “eet” is an imaginary alternative to “tee” 🙂 (-a means append, — separates the captured command) It shouldn’t be hard to hack such a command but maybe it already exists and I’m not aware of it?



Here’s an eet. Works with every Bash I can get my hands on, from 2.05b to 4.0.

while [[ $# > 0 && $1 != -- ]]; do
    tee_args=("${tee_args[@]}" "$1")
# now ${tee_args[*]} has the arguments before --,
# and $* has the arguments after --

# redirect standard out through a pipe to tee
exec | tee "${tee_args[@]}"

# do the *real* exec of the desired program
exec "[email protected]"

(pipefail and $PIPESTATUS are nice, but I recall them being introduced in 3.1 or thereabouts.)


This works with bash:

  set -o pipefail
  mycommand --foo --bar | tee some.log

The parentheses are there to limit the effect of pipefail to just the one command.

From the bash(1) man page:

The return status of a pipeline is the exit status of the last command, unless the pipefail option is enabled. If pipefail is enabled, the pipeline’s return status is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands exit successfully.


Stumbled upon a couple of interesting solutions here http://www.perlmonks.org/?node_id=597613.

1) There is $PIPESTATUS variable available in bash:

   false | tee /dev/null
   [ $PIPESTATUS -eq 0 ] || exit $PIPESTATUS

2) And the simplest prototype of “eet” in perl may look as follows:

   open MAKE, "command 2>&1 |" or die;
   open (LOGFILE, ">>some.log") or die;
   while (<MAKE>) { print LOGFILE $_; print }
   close MAKE; # to get $?
   my $exit = $? >> 8;
   close LOGFILE;


This is what I consider to be the best pure-Bourne-shell solution to use as the base upon which you could build your “eet”:

# You want to pipe command1 through command2:
exec 4>&1
exitstatus=`{ { command1; echo $? 1>&3; } | command2 1>&4; } 3>&1`
# $exitstatus now has command1's exit status.

I think this is best explained from the inside out – command1 will execute and print its regular output on stdout (file descriptor 1), then once it’s done, echo will execute and print command1‘s exit code on its stdout, but that stdout is redirected to file descriptor three.

While command1 is running, its stdout is being piped to command2 (echo‘s output never makes it to command2 because we send it to file descriptor 3 instead of 1, which is what the pipe reads). Then we redirect command2‘s output to file descriptor 4, so that it also stays out of file descriptor one – because we want file descriptor one clear for when we bring the echo output on file descriptor three back down into file descriptor one so that the command substitution (the backticks) can capture it.

The final bit of magic is that first exec 4>&1 we did as a separate command – it opens file descriptor four as a copy of the external shell’s stdout. Command substitution will capture whatever is written on standard out from the perspective of the commands inside it – but, since command2‘s output is going to file descriptor four as far as the command substitution is concerned, the command substitution doesn’t capture it – however, once it gets “out” of the command substitution, it is effectively still going to the script’s overall file descriptor one.

(The exec 4>&1 has to be a separate command to work with many common shells. In some shells it works if you just put it on the same line as the variable assignment, after the closing backtick of the substitution.)

(I use compound commands ({ ... }) in my example, but subshells (( ... )) would also work. The subshell will just cause a redundant forking and awaiting of a child process, since each side of a pipe and the inside of a command substitution already normally implies a fork and await of a child process, and I don’t know of any shell being coded to recognize that it can skip one of those forks because it’s already done or is about to do the other.)

You can look at it in a less technical and more playful way, as if the outputs of the commands are leapfrogging each other: command1 pipes to command2, then the echo‘s output jumps over command2 so that command2 doesn’t catch it, and then command2‘s output jumps over and out of the command substitution just as echo lands just in time to get captured by the substitution so that it ends up in the variable, and command2‘s output goes on its way to the standard output, just as in a normal pipe.

Also, as I understand it, at the end of this command, $? will still contain the return code of the second command in the pipe, because variable assignments, command substitutions, and compound commands are all effectively transparent to the return code of the command inside them, so the return status of command2 should get propagated out.

A caveat is that it is possible that command1 will at some point end up using file descriptors three or four, or that command2 or any of the later commands will use file descriptor four, so to be more hygienic, we would do:

exec 4>&1
exitstatus=`{ { command1 3>&-; echo $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-

Commands inherit file descriptors from the process that launches them, so the entire second line will inherit file descriptor four, and the compound command followed by 3>&1 will inherit the file descriptor three. So the 4>&- makes sure that the inner compound command will not inherit file descriptor four, and the 3>&- makes sure that command1 will not inherit file descriptor three, so command1 gets a ‘cleaner’, more standard environment. You could also move the inner 4>&- next to the 3>&-, but I figure why not just limit its scope as much as possible.

Almost no programs uses pre-opened file descriptor three and four directly, so you almost never have to worry about it, but the latter is probably best to keep in mind and use for general-purpose cases.


Korn shell, ALL in 1 line:

foo; RET_VAL=$?; if test ${RET_VAL} != 0;then echo $RET_VAL; echo Error occurred!>/tmp/out.err;exit 2;fi |tee >>/tmp/out.err ; if test ${RET_VAL} != 0;then exit $RET_VAL;fi


{ mycommand --foo --bar 2>&1; ret=$?; } | tee -a some.log; (exit $ret)


exec 2>&1
exec "[email protected]" | tee "$logfile"

Hopefully this works for you.



Assuming bash or zsh,

my_command >>my_log 2>&1

N.B. Sequence of redirection and duplication of STDERR onto STDOUT is significant!

Edit: Oops. Didn’t realise you wanted to see the output on screen as well. This will of course direct all output to the file my_log.



Leave a Comment