What is the proper way to detect shell exit code when errexit option is set?

I prefer to write solid shell code, so the errexit & nounset is always set.

The following code will stop at bad_command line

#!/bin/bash
set -o errexit
set -o nounset
bad_command # stop here
good_command

I want to capture it, here is my method

#!/bin/bash
set -o errexit
set -o nounset
rc=1
bad_command && rc=0 # stop here
[ $rc -ne 0 ] && do_err_handle
good_command

Is there any better or cleaner method

My Answer:

#!/bin/bash
set -o errexit
set -o nounset
if ! bad_command ; then
  # error handle here
fi
good_command

Solution 1:

How about this? If you want the actual exit code ...

#!/bin/sh                                                                       
set -e

cat /tmp/doesnotexist && rc=$? || rc=$?                                         
echo exitcode: $rc        

cat /dev/null && rc=$? || rc=$?                                                 
echo exitcode: $rc   

Output:

cat: /tmp/doesnotexist: No such file or directory
exitcode: 1
exitcode: 0

Solution 2:

Keep with errexit. It can help find bugs that otherwise might have unpredictable (and hard to detect) results.

#!/bin/bash
set -o errexit ; set -o nounset

bad_command || do_err_handle
good_command

The above will work fine. errexit only requires that the line pass, as you do with the bad_command && rc=0. Therefore, the above with the 'or' will only run do_err_handle if bad_command fails and, as long as do_err_handle doesn't also 'fail', then the script will continue.

Solution 3:

A slight variation of the answer given by @rrauenza. Since the && rc=$? part is their answer will always be equal to && rc=0 one can as well set rc to 0 before running the command. The result ends up more readable in my opinion because the variable is defined upfront in its own line of code and is only changed if the command exits with a non-zero exit status. If nounset is also given, then it's now clear that rc was indeed never undefined. This also avoids mixing && and || in the same line which might be confusing because one might not always know the operator precedence by heart.

#!/bin/sh                                                                       
set -eu

rc=0
cat /tmp/doesnotexist || rc=$?                                         
echo exitcode: $rc        

rc=0
cat /dev/null || rc=$?                                                 
echo exitcode: $rc   

Output:

cat: /tmp/doesnotexist: No such file or directory
exitcode: 1
exitcode: 0

Solution 4:

Continue to write solid shell code.

You do it right if bad_command is really a command. But be careful with function calls in if, while, ||, && or !, because errexit will not work there. It may be dangerous.

If your bad_command is actually bad_function you should write this:

set -eu 

get_exit_code() {
    set +e
    ( set -e;
      "$@"
    )
    exit_code=$?
    set -e
}

...

get_exit_code bad_function

if [ "$exit_code" != 0 ]; then
    do_err_handle
fi

This works well in bash 4.0. In bash 4.2 you only get exit codes 0 or 1.

Solution 5:

set -o errexit

bad_command || {
  resp_code=$?
  echo Bad Thing $resp_code happened
}