I prefer to write solid shell code, so the errexit & nounset is alway set.
The following code will stop at bad_command line
#!/bin/b
The accepted answer is good, but I think it could be refactored to be even better; more generic, easier to refactor and read:
some_command_status=$(some_command && echo $? || echo $?)
vs.
some_command && some_command_status=$? || some_command_status=$?
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.
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
I cobbled together a (hopefully) textbook example from all the answers:
#!/usr/bin/env bash
# exit immediately on error
set -o errexit
file='dfkjlfdlkj'
# Turn off 'exit immediately on error' during the command substitution
blah=$(set +o errexit && ls $file) && rc=$? || rc=$?
echo $blah
# Do something special if $rc
(( $rc )) && echo failure && exit 1
echo success
In case you want to detect the exit code of a compound list (or a function), and have errexit
and nounset
applied there, you can use this kind of code:
#!/bin/sh
set -eu
unset -v unbound_variable
f() {
echo "before false"
false
echo "after false"
}
g() {
echo "before unbound"
var=$unbound_variable
echo "after unbound"
}
set +e
(set -e; f)
echo error code of f = $?
set -e
echo still in main program
set +e
(set -e; g)
echo error code of g = $?
set -e
echo still in main program
The above should print non-zero error codes for both functions f
and g
. I suppose this works by any POSIX shell. You can also detect the error code in EXIT trap, but the shell exits thereafter. The problem with other proposed methods is that the errexit
setting is ignored when an exit status of such a compound list is tested. Here is a quote from the POSIX standard:
The -e setting shall be ignored when executing the compound list following the while, until, if, or elif reserved word, a pipeline beginning with the ! reserved word, or any command of an AND-OR list other than the last.
Note that if you have defined your function like
f() (
set -e
...
)
it is enough to do
set +e
f
echo exit code of f = $?
set -e
to get the exit code.
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