274

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?

0

4 Answers 4

413

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
Sign up to request clarification or add additional context in comments.

12 Comments

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.
Or do a loop over PIPESTATUS
to restore the original pipe setting: $ set +o pipefail
Note, pipefail is also supported by dash on ubuntu
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
|
226

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

mvn clean install $@ | tee $logfile
echo ${PIPESTATUS[0]}

2 Comments

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.
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.
21

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.

3 Comments

I hope the final answer isn't shell specific (ie: bash only).
Perfect! I prefer this one to not rely only on bash :-) Thanks @Demosthenex
But I used ( ) instead of { } to capture output ....
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

2 Comments

Could we save a fork with read e < $f instead of the cat?
sure. and you probably want to create a tempfile to avoid race conditions. but, as you can see there are better solutions...

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.