Piping command output to tee but also save exit code of command [duplicate]
Asked Answered
C

4

257

I have a shell script in which I wrap a command (mvn clean install), to redirect the output to a logfile.

#!/bin/bash
...
mvn clean install $@ | tee $logfile
echo $? # Does not show the return code of mvn clean install

Now if mvn clean install fails with an error, I want my wrapper shell script also fail with that error. But since I'm piping all the output to tee, I cannot access the return code of mvn clean install, so when I access $? afterwards, it's always 0 (since tee successes).

I tried letting the command write the error output to a separate file and checking that afterwards, but the error output of mvn is always empty (seems like it only writes to stdout).

How can I preserve the return code of mvn clean install but still piping the output to a logfile?

Cailly answered 29/7, 2011 at 10:36 Comment(0)
A
217

Since you're running bash, you can use its $PIPESTATUS variable instead of $?:

mvn clean install $@ | tee $logfile
echo ${PIPESTATUS[0]}
Almsgiver answered 29/7, 2011 at 10:42 Comment(2)
As mentioned below if you have more than a single pipe then you'll need to check the status of each command to know where it failed.Roanna
Also very important: the variable is ephemeral, so even "echo"-ing it out will lose you the value from running the pipeline. Assign it to another variable unless you will access it immediately, and only once.Anta
S
391

You can set the pipefail shell option option on to get the behavior you want.

From the Bash Reference Manual:

The exit status of a pipeline is the exit status of the last command in the pipeline, unless the pipefail option is enabled (see The Set Builtin). 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.

Example:

$ false | tee /dev/null ; echo $?
0
$ set -o pipefail
$ false | tee /dev/null ; echo $?
1

To restore the original pipe setting:

$ set +o pipefail
Santalaceous answered 29/7, 2011 at 11:5 Comment(10)
This seems a more elegant solution than the accepted solutionMetapsychology
Agreed. The accepted answer requires you to know in advance which command in your pipes will fail. If you are piping 5 different commands together, you will have to guess which one in the array will fail.Maiamaiah
Or do a loop over PIPESTATUSRoanna
to restore the original pipe setting: $ set +o pipefailUncertainty
Note, pipefail is also supported by dash on ubuntuBurnout
Great answer, however I would point out, that set +o pipfail will turn OFF the pipefail (not return it to its original state) as the docs say Using ‘+’ rather than ‘-’ causes these options to be turned **off**. The options can also be used upon invocation of the shell. The current set of options may be found in $-.Tropic
Also if you're going for a one-liner: bash -c "set -o pipefail && false | tee log.txt" will exit with an error code. Very useful for something like a build process that you're logging.Theirs
if tee is killed, it doesn't return...Goldplate
imho this should be the correct answer: (set -o pipefail && false | tee log.txt) execute the stuff in a sub-shell so that the original state of the pipefail flag will be restoredObserver
what is the scope of pipefail if not run in a subshell as @JiriKremser suggests? I.e. if the script fails before set +o pipefail is called, will pipefail still be enabled when other scripts are called?Dyedinthewool
A
217

Since you're running bash, you can use its $PIPESTATUS variable instead of $?:

mvn clean install $@ | tee $logfile
echo ${PIPESTATUS[0]}
Almsgiver answered 29/7, 2011 at 10:42 Comment(2)
As mentioned below if you have more than a single pipe then you'll need to check the status of each command to know where it failed.Roanna
Also very important: the variable is ephemeral, so even "echo"-ing it out will lose you the value from running the pipeline. Assign it to another variable unless you will access it immediately, and only once.Anta
F
19

You could run the mvn command and cache the exit code... I use the "false" command for my example.

$ { false ; echo $? > /tmp/false.status ; } | tee $logfile
$ cat /tmp/false.status
1

That way you can use the status file content to make further decisions.

I'm curious now whether there is a more eloquent way to accomplish this.

Franconian answered 29/7, 2011 at 10:55 Comment(3)
I hope the final answer isn't shell specific (ie: bash only).Franconian
Perfect! I prefer this one to not rely only on bash :-) Thanks @FranconianStigmasterol
But I used ( ) instead of { } to capture output ....Stigmasterol
D
4

Workaround (note: a perfer @Frederic's solution):

f=`mktemp`
(mvn clean install $@; echo $?>$f) | tee $logfile
e=`cat $f` #error in variable e
rm $f
Deloisedelong answered 29/7, 2011 at 10:56 Comment(2)
Could we save a fork with read e < $f instead of the cat?Choosey
sure. and you probably want to create a tempfile to avoid race conditions. but, as you can see there are better solutions...Deloisedelong

© 2022 - 2024 — McMap. All rights reserved.