I have a Bash shell script that invokes a number of commands.

I would like to have the shell script automatically exit with a return value of 1 if any of the commands return a non-zero value.

Is this possible without explicitly checking the result of each command?

For example,

if [[ $? -ne 0 ]]; then
    exit 1

if [[ $? -ne 0 ]]; then
    exit 1

Solution 1

Add this to the beginning of the script:

set -e

This will cause the shell to exit immediately if a simple command exits with a nonzero exit value. A simple command is any command not part of an if, while, or until test, or part of an && or || list.

See the bash(1) man page on the "set" internal command for more details.

I personally start almost all shell scripts with "set -e". It's really annoying to have a script stubbornly continue when something fails in the middle and breaks assumptions for the rest of the script.

Solution 2

To add to the accepted answer:

Bear in mind that set -e sometimes is not enough, specially if you have pipes.

For example, suppose you have this script

set -e 
./configure  > configure.log

... which works as expected: an error in configure aborts the execution.

Tomorrow you make a seemingly trivial change:

set -e 
./configure  | tee configure.log

... and now it does not work. This is explained here, and a workaround (Bash only) is provided:

set -e 
set -o pipefail

./configure  | tee configure.log

Solution 3

The if statements in your example are unnecessary. Just do it like this:

dosomething1 || exit 1

If you take Ville Laurikari's advice and use set -e then for some commands you may need to use this:

dosomething || true

The || true will make the command pipeline have a true return value even if the command fails so the the -e option will not kill the script.

Solution 4

If you have cleanup you need to do on exit, you can also use 'trap' with the pseudo-signal ERR. This works the same way as trapping INT or any other signal; bash throws ERR if any command exits with a nonzero value:

# Create the trap with   
trap "rm -f /tmp/$MYTMPFILE; exit 1" ERR INT TERM
# Partially turn off the trap.
trap - ERR
# Now a control-C will still cause cleanup, but
# a nonzero exit code won't:
ps aux | grep blahblahblah

Or, especially if you're using "set -e", you could trap EXIT; your trap will then be executed when the script exits for any reason, including a normal end, interrupts, an exit caused by the -e option, etc.

Solution 5

The $? variable is rarely needed. The pseudo-idiom command; if [ $? -eq 0 ]; then X; fi should always be written as if command; then X; fi.

The cases where $? is required is when it needs to be checked against multiple values:

case $? in
  (0) X;;
  (1) Y;;
  (2) Z;;

or when $? needs to be reused or otherwise manipulated:

if command; then
  echo "command successful" >&2
  echo "command failed with exit code $ret" >&2
  exit $ret

Solution 6

Run it with -e or set -e at the top.

Also look at set -u.

Solution 7

On error, the below script will print a RED error message and exit.
Put this at the top of your bash script:

# BASH error handling:
#   exit on command failure
set -e
#   keep track of the last executed command
#   on error: print the failed command
trap 'ERROR_CODE=$?; FAILED_COMMAND=$LAST_COMMAND; tput setaf 1; echo "ERROR: command \"$FAILED_COMMAND\" failed with exit code $ERROR_CODE"; put sgr0;' ERR INT TERM

Solution 8

#!/bin/bash -e

should suffice.

Solution 9

An expression like

dosomething1 && dosomething2 && dosomething3

will stop processing when one of the commands returns with a non-zero value. For example, the following command will never print "done":

cat nosuchfile && echo "done"
echo $?

Solution 10

I am just throwing in another one for reference since there was an additional question to Mark Edgars input and here is an additional example and touches on the topic overall:

[[ `cmd` ]] && echo success_else_silence

Which is the same as cmd || exit errcode as someone showed.

For example, I want to make sure a partition is unmounted if mounted:

[[ `mount | grep /dev/sda1` ]] && umount /dev/sda1