Solution
If you are running Bash 4.4 or later, you can use the shopt
option inherit_errexit
to do just that. You can check compatibility from within Bash using echo $BASH_VERSION
.
Here is the shebang you would use if Bash 4.4 or later were installed and came before /bin
in your $PATH
:
#!/usr/bin/env -S bash -euET -o pipefail -O inherit_errexit
The -S
is there to coax Linux’s env
into accepting more than one argument for bash
, as kindly pointed out by @UVV and explained further on StackOverflow.
Background
inherit_errexit
is an option to shopt
, while the rest of the arguments are options to set
. In most modern iterations, they can be passed directly to bash
when invoking the shell.
Let’s review the options you have already been using:
-u
/-o nounset
, as the name ambiguously hints, disallows dereferencing of variables that have not been set; e.g., $IJUSTMADETHISUP
.
-e
/-o errexit
does some of what you are requesting: it causes directly called shell commands with nonzero return values to cause the shell to exit entirely.
-o pipefail
is needed to extend this to commands whose output is redirected with an I/O pipe |
.
Now for the options I’ve added:
-O inherit_errexit
further extends this functionality (exiting on nonzero status code) to commands called from within subshells $(...)
.
- The
-E
/-o errtrace
and -T
/-o functrace
options are there for the comparatively rare case that you use trap
to perform an action when the shell receives a signal. These two options extend signal handlers to the inner bodies of shell functions for ERR
signals and DEBUG
/RETURN
signals, respectively.
See also
You can't tell. All you get is a single value between 0 and 255, which is 0 if everything went well and nonzero otherwise.
If you want to treat some nonzero statuses as successes, be sure that the command in question can't fail for other reasons such as a redirection. Break up the command so that different kinds of failures happen in different commands or result in different statuses.
For example, if you need to know whether an error comes from a redirection, either do the redirection in a separate command, or do it separately over a block.
Combined status:
mycommand <foo
status=$?
if [ $status -ne 0 ]; then echo "Either mycommand failed or <foo failed"; fi
Separate statuses, but no way to avoid running the command if the redirection fails:
{
mycommand
command_status=$?
} <foo
redirection_status=$?
if [ $command_status -ne 0 ]; then echo "mycommand failed"; fi
if [ $redirection_status -ne 0 ]; then echo "<foo failed"; fi
Do the redirection first. Note that being able to react to the failure of the redirection in this way is a bash feature. POSIX shells, including bash in POSIX mode, exit if a redirection on the exec
builtin fails.
exec 3<&1 # Save stdin to file descriptor 3
exec <foo # bash keeps going if the redirection fails
redirection_status=$?
mycommand
command_status=$?
exec <&3 # Restore stdin
if [ $command_status -ne 0 ]; then echo "mycommand failed"; fi
if [ $redirection_status -ne 0 ]; then echo "<foo failed"; fi
Do the redirection fails, in a subshell to contain the failure of the redirection and not have to restore the file descriptor afterwards.
(
exec <foo || exit $? # In POSIX sh, "|| exit $?" is redundant.
mycommand
command_status=$?
if [ $command_status -ne 0 ]; then echo "mycommand failed"; fi
)
redirection_status=$?
if [ $redirection_status -ne 0 ]; then echo "<foo failed and mycommand didn't run"; fi
If you need to know whether an error comes from some other expansion, do the expansion separately and save its result.
Saving one argument: instead of `mycommand "$(…)", save the result of the expansion first.
foo=$(…) && mycommand "$foo"
More generally:
foo=$(…)
command_substitution_status=$?
mycommand "$foo"
mycommand_status=$?
Note that if an assignment contains multiple command substitutions, its status is the status of the last substitution: the status is 0 if the last substitution succeeds, even if earlier ones failed.
foo=$(…)
foo_status=$?
bar=$(…)
bar_status=$?
mycommand "$foo" "$bar"
mycommand_status=$?
To save multiple arguments, use an array, or the positional parameters inside a function.
args=()
foo=$(…)
foo_status=$?
args+=(-x "$foo")
bar=$(…)
bar_status=$?
args+=(-y "$bar")
mycommand "${args[@]}"
Best Answer
Replace:
with:
This will cause the code to exit if the
for
loop exits with a non-zero exit code.As a point of trivia, the
1
inexit 1
is not needed. A plainexit
command would exit with the exit status of the last executed command which would befalse
(code=1) if the download fails. If the download succeeds, the exit code of the loop is the exit code of theecho
command.echo
normally exits with code=0, signally success. In that case, the||
does not trigger and theexit
command is not executed.Lastly, note that
set -o errexit
can be full of surprises. For a discussion of its pros and cons, see Greg's FAQ #105.Documentation
From
man bash
: