set -e doesn't trigger on failing commands that are used as conditions like in the condition section of
until constructs or on the left of a
&& or in functions, subshells, sourced files,
evaled code that are invoked under those conditions.
If it did, then:
if [ ! -f /custom.log ]; then
Would exit the script if
/custom.log was a regular file as
[ would then also exit with a non-zero exit status.
[ builtin command of the
bash shell (and most other implementations) exits with a
1 status if the tested condition is not met, and
2 if there's a syntax error (not all syntax errors though, for instance, not in
[ -v 'a[+]' ]). POSIX requires the exit status to be greater than 1 in case of error.
So you could choose to exit the script if a command exits with a code greater than 1 regardless of whether it's used in a condition or not with something like:
shopt -s extdebug # make sure the DEBUG trap propagates to subshells
trap '(($?>1 && (ret=$?))) && exit "$ret"' DEBUG
[ -f / ] || echo / not a regular file # OK
[ -f /] || echo was a syntax error # causes an exit, not output
echo not reached
Note that you can't use the
ERR trap for that as the
ERR trap is only run in the same condition as those that trigger the exit by
Now, beware of the implications. For instance, that would cause a:
if grep -qs pattern /file; then
echo pattern was found in /file
to exit if
/file didn't exist or wasn't readable, as
grep returns with a 2 status in that case, even though with
-s, the intention was clearly to ignore those cases.
So you'll need to beware of upon which conditions the commands you use in your conditions may exit with a status greater than 1. To work around those, you'd need something like:
if sh -c 'grep -sq pattern / file || exit 1'; then...
You could restrict the exit upon exit status greater than 1 to the
test command with something like:
unset -v previous_BASH_COMMAND
case $previous_BASH_COMMAND in
("[ "* | "test "*) (($?>1 && (ret=$?))) && exit "$ret"
That has a few limitations. In
([ -f/]; echo y)
That would cause the subshell to exit, but not the parent as the
$previous_BASH_COMMAND has not been set there. And in:
[ -f / ] && echo a regular file
(grep -qs foo /file && echo foo in /file)
The shell would be exited upon running
echo here, because
$? would be 2 and
[ -f / ].
In any case, things like
[ -f /] | cat
export var="$([ -f /])"
could not be detected as the exit status is not propagated to the parent shell process (except with the
pipefail option in the first case).
Now, I'm not sure it's worth the trouble adding this kind of (brittle) detection at run time, when the error is easily detectable at development time (when you write and test your script).