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 oneecho
statement, otherwise subsequentecho
statements will reflect the status of the previousecho
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.
An excellent tip. Apologies for not looking back through the history of the tips in case this suggestion is already covered, but bash comparisons can be a headache (and not just for newbies) with differences between string and numeric often leading to bash errors. A tip or two on bash comparison operators would be useful for some readers.