We're an ISO27001:2013 Certified Supplier

blog-post-featured-image

Can you spot the error in this bash script?

LOG="/usr/bin/logger -t "mycommand" --"
/usr/bin/mycommand 2>&1 | $LOG
if [ $? -ne 0 ]; then
    $LOG "mycommand failed"
    ...

Maybe the title of this TechTip gives it away, but the exit status check (if [ $? -ne 0 ]) is checking the exit status of the logger command, not the exit status of mycommand.

The intention here, of course, is to capture any output from mycommand sent to either sdtout or stderr, and log it via the system logger. That part works nicely. What fails is the check to see whether mycommand exited with an error status.

There are a number of solutions to this problem.

Testing

To test any potential solution, we can use false(1), which the man page succinctly describes as “do nothing, unsuccessfully”. In other words, it does nothing and exits with a status code of 1:

$ false
$ echo $?
1

Its counterpart is true(1) (“do nothing, successfully”):

$ true
$ echo $?
0

Catch All Failures

The first solution is to set the (bash) pipefail option, which is described in the bash(1) man page: “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.”

In the original example at the top of the page, the first command in a pipe sequence failed, but the error wasn’t caught:

$ false | true
$ echo $?
0

If we set the pipefail option, the exit status of the first failure – the false command in this example – will be the final exit status:

$ set -o pipefail
$ false | true
$ echo $?
1

Catch All Statuses

Another way of detecting failures like this is by using the PIPESTATUS variable. This is an array variable with one entry per pipe command:

$ false | true
$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
1 0

Note that:

  • the entire variable, including the index (‘[0]‘), needs to be enclosed in braces so that the shell sees the index as part of the shell variable
  • when examined with echo, as above, all of the elements of the PIPESTATUS variable must be examined in one echo statement, otherwise subsequent echo statements will reflect the status of the previous echo statements:
$ false | true
$ echo "${PIPESTATUS[0]}" # exit status of the false command
1
$ echo "${PIPESTATUS[1]}" # exit status of the non-existent second command after echo

$

Everyday Use

When checking the output status of a pipe command, it’s very easy to miss the subtlety that, by default, you are only checking the output of the last command.

Typically, we care whether or not the entire pipe sequence completed successfully. We don’t normally care at which point a failure occurred, so in everyday use the set -o pipefail is the easiest to use.

Was This TechTip Helpful?

Let us know in the comments below.