I want to execute a long running command in Bash, and both capture its exit status, and tee its output.
So I do this:
command | tee out.txt
ST=$?
The problem is that the variable ST captures the exit status of tee
and not of command. How can I solve this?
Note that command is long running and redirecting the output to a file to view it later is not a good solution for me.
Outside of bash, you can do:
This is useful for example in ninja scripts where the shell is expected to be
/bin/sh
.There is an internal Bash variable called
$PIPESTATUS
; it’s an array that holds the exit status of each command in your last foreground pipeline of commands.Or another alternative which also works with other shells (like zsh) would be to enable pipefail:
The first option does not work with
zsh
due to a little bit different syntax.Pure shell solution:
And now with the second
cat
replaced byfalse
:Please note the first cat fails as well, because it's stdout gets closed on it. The order of the failed commands in the log is correct in this example, but don't rely on it.
This method allows for capturing stdout and stderr for the individual commands so you can then dump that as well into a log file if an error occurs, or just delete it if no error (like the output of dd).
There's an array that gives you the exit status of each command in a pipe.
using bash's
set -o pipefail
is helpfulPIPESTATUS[@] must be copied to an array immediately after the pipe command returns. Any reads of PIPESTATUS[@] will erase the contents. Copy it to another array if you plan on checking the status of all pipe commands. "$?" is the same value as the last element of "${PIPESTATUS[@]}", and reading it seems to destroy "${PIPESTATUS[@]}", but I haven't absolutely verified this.
This will not work if the pipe is in a sub-shell. For a solution to that problem,
see bash pipestatus in backticked command?