Reputation: 16012
I prefer to write solid shell code, so the errexit & nounset is always set.
The following code will stop after bad_command
.
#!/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_handling
good_command
Is there any better or cleaner method?
My Answer:
#!/bin/bash
set -o errexit
set -o nounset
if ! bad_command ; then
# error handling here
fi
good_command
Upvotes: 65
Views: 35455
Reputation: 7548
In bash you can use the trap builtin which is quite nice. See https://unix.stackexchange.com/questions/79648/how-to-trigger-error-using-trap-command
Not sure how portable it is with other shells.. so YMMV
Edit.. comment below informs me that this is not portable so take care.
Upvotes: 1
Reputation: 151
I tried the early answers and they did not work when calling a program that segfaults. My use case is trying to catch a rare segfault and reporting which of a few thousand inputs caused it to occur. Instead, I based it on
if [ $(./segfault >realstdout || echo error) == "error" ]
then
echo this died # and report details
else
echo strange success
fi
This seems convoluted but I have not found a cleaner way.
Upvotes: 0
Reputation: 313
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.
Update: No problem with exit codes in bash 5.0.
Upvotes: 8
Reputation: 856
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
, though you might want that the script exits immediately after unbound variable error. 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.
Upvotes: 4
Reputation: 124
Just trying to complete the following answer: https://stackoverflow.com/a/32201766/2609399 , which is a very good catch, BTW.
I've faced this limitation a time ago and applied a similar fix for it. Please consider the below code sample.
invokeBashFunction() {
local functionExitCode="0"
/bin/bash -c "
set -o errexit
${*}
" || functionExitCode="${?}"
# add some additional processing logic/code
return "${functionExitCode}"
}
export -f invokeBashFunction
And there are few example of how to use it:
invokeBashFunction bad_function param1 "param 2" param3 && echo "It passed." || echo "It failed!"
if invokeBashFunction bad_function param1 "param 2" param3
then
echo "It passed."
fi
if ! invokeBashFunction bad_function param1 "param 2" param3
then
echo "It failed!"
fi
Upvotes: 1
Reputation: 1110
A clean reliable way to error exit
command_that_error_exits || { echo "Line $LINENO: Failed with Error" 1>&2; exit 1;}
Upvotes: 1
Reputation: 7164
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
Upvotes: 9
Reputation: 511
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
Upvotes: 0
Reputation: 8134
A common way to avoid exiting a Bash program when errexit
is set and a command that may fail is run is to precede the command with !
.
After the command has run $?
does not contain the exit status of the command. (It contains 0 if the command failed and 1 otherwise.) However, the PIPESTATUS array does contain the exit status of the command. A safe way to capture the exit status of a command that may fail, whether or not errexit
is set, is:
! bad_command
rc=${PIPESTATUS[0]}
The second line can be simplified to rc=$PIPESTATUS
, but Shellcheck will complain about it.
If (as is often the case) you don't need to know the exit status of the command, just if it succeeded or failed, then the solution by @george is good if the error handler is a one-liner. For multi-line error handlers a good option is:
if ! bad_command ; then
# Handle errors here
fi
Note that (except in very unusual circumstances) it would not be correct to use `bad_command`
(as suggested in the question) instead of plain bad_command
. You can use ${PIPESTATUS[0]}
to get the exit status of the command if it is needed in the error handling code, since $?
doesn't contain it in this case either.
Upvotes: 4
Reputation: 309
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=$?
Upvotes: -1
Reputation: 6993
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
Upvotes: 58
Reputation: 71
set -o errexit bad_command || { resp_code=$? echo Bad Thing $resp_code happened }
Upvotes: 7
Reputation: 21
what if you want to know exit status of bad_command?
I think the simplest way is to disable errexit:
#!/bin/sh
set -o errexit
some_code_here
set +o errexit
bad_command
status=$?
set -o errexit
process $status
Upvotes: 1
Reputation: 271
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.
Upvotes: 27
Reputation: 37288
Agree with comments, so if you can give up errexit
then you can easily shorten your code to
bad_command || do_err_handle
good_command
I hope this helps.
Upvotes: 3