Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

bash: redirect (and append) stdout and stderr to file and terminal and get proper exit status

To redirect (and append) stdout and stderr to a file, while also displaying it on the terminal, I do this:

command 2>&1 | tee -a file.txt 

However, is there another way to do this such that I get an accurate value for the exit status?

That is, if I test $?, I want to see the exit status of command, not the exit status of tee.

I know that I can use ${PIPESTATUS[0]} here instead of $?, but I am looking for another solution that would not involve having to check PIPESTATUS.

like image 278
rouble Avatar asked Mar 09 '10 22:03

rouble


People also ask

How do I redirect stderr and stdout to a file in bash?

Understanding the concept of redirections and file descriptors is very important when working on the command line. To redirect stderr and stdout , use the 2>&1 or &> constructs.


2 Answers

Perhaps you could put the exit value from PIPESTATUS into $?

command 2>&1 | tee -a file.txt ; ( exit ${PIPESTATUS} ) 
like image 174
Martin Avatar answered Oct 05 '22 10:10

Martin


Another possibility, with some bash flavours, is to turn on the pipefail option:

pipefail

If set, the return value of a pipeline is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands in the pipeline exit successfully. This option is disabled by default.

set -o pipefail ... command 2>&1 | tee -a file.txt || echo "Command (or tee?) failed with status $?" 

This having been said, the only way of achieving PIPESTATUS functionality portably (e.g. so it'd also work with POSIX sh) is a bit convoluted, i.e. it requires a temp file to propagate a pipe exit status back to the parent shell process:

{ command 2>&1 ; echo $? >"/tmp/~pipestatus.$$" ; } | tee -a file.txt if [ "`cat \"/tmp/~pipestatus.$$\"`" -ne 0 ] ; then   ... fi 

or, encapsulating for reuse:

log2file() {   LOGFILE="$1" ; shift   { "$@" 2>&1 ; echo $? >"/tmp/~pipestatus.$$" ; } | tee -a "$LOGFILE"   MYPIPESTATUS="`cat \"/tmp/~pipestatus.$$\"`"   rm -f "/tmp/~pipestatus.$$"   return $MYPIPESTATUS }  log2file file.txt command param1 "param 2" || echo "Command failed with status $?" 

or, more generically perhaps:

save_pipe_status() {   STATUS_ID="$1" ; shift   "$@"   echo $? >"/tmp/~pipestatus.$$.$STATUS_ID" }  get_pipe_status() {   STATUS_ID="$1" ; shift   return `cat "/tmp/~pipestatus.$$.$STATUS_ID"` }  save_pipe_status my_command_id ./command param1 "param 2" | tee -a file.txt get_pipe_status my_command_id || echo "Command failed with status $?"  ...  rm -f "/tmp/~pipestatus.$$."* # do this in a trap handler, too, to be really clean 
like image 22
vladr Avatar answered Oct 05 '22 12:10

vladr