set -e
(or a script starting with #!/bin/sh -e
) is extremely useful to automatically bomb out if there is a problem. It saves me having to error ch
If a subshell isn't an option (say you need to do something crazy like set a variable) then you can just check every single command that might fail and deal with it by appending || return $?
. This causes the function to return the error code on failure.
It's ugly, but it works
#!/bin/sh
set -e
my_function() {
echo "the following command could fail:"
false || return $?
echo "this is after the command that fails"
}
if ! my_function; then
echo "dealing with the problem"
fi
echo "run this all the time regardless of the success of my_function"
gives
the following command could fail:
dealing with the problem
run this all the time regardless of the success of my_function
This is a bit of a kludge, but you can do:
export -f f if sh -ec f; then ...
This will work if your shell supports export -f (bash does).
Note that this will not terminate the script. The echo after the false in f will not execute, nor will the body of the if, but statements after the if will be executed.
If you are using a shell that does not support export -f, you can get the semantics you want by running sh in the function:
f() { sh -ec ' echo This will execute false echo This will not ' }
This is by design and POSIX specification. We can read in man bash
:
If a compound command or shell function executes in a context where
-e
is being ignored, none of the commands executed within the compound command or function body will be affected by the-e
setting, even if-e
is set and a command returns a failure status. If a compound command or shell function sets-e
while executing in a context where-e
is ignored, that setting will not have any effect until the compound command or the command containing the function call completes.
therefore you should avoid relying on set -e
within functions.
Given the following exampleAustin Group:
set -e
start() {
some_server
echo some_server started successfully
}
start || echo >&2 some_server failed
the set -e
is ignored within the function, because the function is a command in an AND-OR list other than the last.
The above behaviour is specified and required by POSIX (see: Desired Action):
The
-e
setting shall be ignored when executing the compound list following thewhile
,until
,if
, orelif
reserved word, a pipeline beginning with the!
reserved word, or any command of an AND-OR list other than the last.
I know this isn't what you asked, but you may or may not be aware that the behavior you seek is built into "make". Any part of a "make" process that fails aborts the run. It's a wholly different way of "programming", though, than shell scripting.
I eventually went with this, which apparently works. I tried the export method at first, but then found that I needed to export every global (constant) variable the script uses.
Disable set -e
, then run the function call inside a subshell that has set -e
enabled. Save the exit status of the subshell in a variable, re-enable set -e, then test the var.
f() { echo "a"; false; echo "Should NOT get HERE"; }
# Don't pipe the subshell into anything or we won't be able to see its exit status
set +e ; ( set -e; f ) ; err_status=$?
set -e
## cleaner syntax which POSIX sh doesn't support. Use bash/zsh/ksh/other fancy shells
if ((err_status)) ; then
echo "f returned false: $err_status"
fi
## POSIX-sh features only (e.g. dash, /bin/sh)
if test "$err_status" -ne 0 ; then
echo "f returned false: $err_status"
fi
echo "always print this"
You can't run f
as part of a pipeline, or as part of a &&
of ||
command list (except as the last command in the pipe or list), or as the condition in an if
or while
, or other contexts that ignore set -e
. This code also can't be in any of those contexts, so if you use this in a function, callers have to use the same subshell / save-exit-status trickery. This use of set -e
for semantics similar to throwing/catching exceptions is not really suitable for general use, given the limitations and hard-to-read syntax.
trap err_handler_function ERR
has the same limitations as set -e
, in that it won't fire for errors in contexts where set -e
won't exit on failed commands.
You might think the following would work, but it doesn't:
if ! ( set -e; f );then ##### doesn't work, f runs ignoring -e
echo "f returned false: $?"
fi
set -e
doesn't take effect inside the subshell because it remembers that it's inside the condition of an if
. I thought being a subshell would change that, but only being in a separate file and running a whole separate shell on it would work.
Join all commands in your function with the &&
operator. It's not too much trouble and will give the result you want.