244

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?

Wolkenarchitekt
  • 20,170
  • 29
  • 111
  • 174

4 Answers4

367

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
eddie
  • 1,252
  • 3
  • 15
  • 20
Jukka Matilainen
  • 9,608
  • 1
  • 25
  • 19
  • 43
    This seems a more elegant solution than the accepted solution – Stefaan Nov 20 '12 at 14:48
  • 4
    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. – noahlz May 31 '13 at 21:58
  • 6
    Or do a loop over PIPESTATUS – Joshua Olson Aug 23 '13 at 02:10
  • 4
    to restore the original pipe setting: `$ set +o pipefail` – eddie Sep 27 '16 at 14:52
  • 1
    Note, `pipefail` is also supported by `dash` on ubuntu – lightswitch05 Jun 07 '17 at 16:41
  • 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 $-.` – jave.web Sep 23 '19 at 17:49
  • 1
    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. – Robert Jun 09 '20 at 01:57
  • if `tee` is killed, it doesn't return... – Alexis Jan 23 '22 at 07:37
  • 3
    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 restored – Jiri Kremser Oct 24 '22 at 11:11
215

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

mvn clean install $@ | tee $logfile
echo ${PIPESTATUS[0]}
Frédéric Hamidi
  • 258,201
  • 41
  • 486
  • 479
  • 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. – Joshua Olson Aug 23 '13 at 02:11
  • 5
    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. – Davor Cubranic Sep 12 '20 at 01:49
18

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.

FooF
  • 4,323
  • 2
  • 31
  • 47
Demosthenex
  • 4,343
  • 2
  • 26
  • 22
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
Karoly Horvath
  • 94,607
  • 11
  • 117
  • 176