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

前端 未结 4 441
离开以前
离开以前 2020-12-23 20:13

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
相关标签:
4条回答
  • 2020-12-23 20:55

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

    command 2>&1 | tee -a file.txt ; ( exit ${PIPESTATUS} )
    
    0 讨论(0)
  • 2020-12-23 20:55

    Use process substitution:

    command > >( tee -a "$logfile" ) 2>&1
    

    tee runs in a subshell so $? holds the exit status of command.

    0 讨论(0)
  • 2020-12-23 21:09

    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
    
    0 讨论(0)
  • 2020-12-23 21:10

    There is an arcane POSIX way of doing this:

    exec 4>&1; R=$({ { command1; echo $? >&3 ; } | { command2 >&4; } } 3>&1); exec 4>&-
    

    It will set the variable R to the return value of command1, and pipe output of command1 to command2, whose output is redirected to the output of parent shell.

    0 讨论(0)
提交回复
热议问题