I have a shell script that executes a number of commands. How do I make the shell script exit if any of the commands exit with a non-zero exit code?
9 Answers
After each command, the exit code can be found in the $?
variable so you would have something like:
ls -al file.ext
rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi
You need to be careful of piped commands since the $?
only gives you the return code of the last element in the pipe so, in the code:
ls -al file.ext | sed 's/^/xx: /"
will not return an error code if the file doesn't exist (since the sed
part of the pipeline actually works, returning 0).
The bash
shell actually provides an array which can assist in that case, that being PIPESTATUS
. This array has one element for each of the pipeline components, that you can access individually like ${PIPESTATUS[0]}
:
pax> false | true ; echo ${PIPESTATUS[0]}
1
Note that this is getting you the result of the false
command, not the entire pipeline. You can also get the entire list to process as you see fit:
pax> false | true | false; echo ${PIPESTATUS[*]}
1 0 1
If you wanted to get the largest error code from a pipeline, you could use something like:
true | true | false | true | false
rcs=${PIPESTATUS[*]}; rc=0; for i in ${rcs}; do rc=$(($i > $rc ? $i : $rc)); done
echo $rc
This goes through each of the PIPESTATUS
elements in turn, storing it in rc
if it was greater than the previous rc
value.
-
39Same feature in just one line of portable code:
ls -al file.ext || exit $?
( [[ ]] is not portable )– MarcHCommented Nov 10, 2010 at 23:44 -
19MarcH, I think you'll find that
[[ ]]
is pretty portable inbash
, which is what the question is tagged :-) Strangely enough,ls
doesn't work incommand.com
so it's not portable either, specious I know, but it's the same sort of argument you present. Commented Nov 11, 2010 at 0:04 -
39I know this is ancient, but it should be noted that you can get the exit code of commands in a pipe via the array
PIPESTATUS
(i.e.,${PIPESTATUS[0]}
for the first command,${PIPESTATUS[1]}
for the second, or${PIPESTATUS[*]}
for a list of all exit stati.– DevSolarCommented Jul 19, 2012 at 15:13 -
11It needs to be emphasized that elegant and idiomatic shell scripting very rarely needs to examine
$?
directly. You usually want something likeif ls -al file.ext; then : nothing; else exit $?; fi
which of course like @MarcH says is equivalent tols -al file.ext || exit $?
but if thethen
orelse
clauses are somewhat more complex, it is more maintainable.– tripleeeCommented Aug 23, 2012 at 7:14 -
9
[[ $rc != 0 ]]
will give you an0: not found
or1: not found
error. This should be changed to[ $rc -ne 0 ]
. Alsorc=$?
could then be removed and just used[ $? -ne 0 ]
. Commented May 21, 2013 at 15:15
If you want to work with $?
, you'll need to check it after each command, since $?
is updated after each command exits. This means that if you execute a pipeline, you'll only get the exit code of the last process in the pipeline.
Another approach is to do this:
set -e
set -o pipefail
If you put this at the top of the shell script, it looks like Bash will take care of this for you. As a previous poster noted, "set -e" will cause Bash to exit with an error on any simple command. "set -o pipefail" will cause Bash to exit with an error on any command in a pipeline as well.
See here or here for a little more discussion on this problem. Here is the Bash manual section on the set
builtin.
-
6This should really be the top answer: it's much, much easier to do this than it is to use
PIPESTATUS
and check exit codes everywhere.– canduCommented Feb 10, 2016 at 18:12 -
2
#!/bin/bash -e
is the only way to start a shell script. You can always use things likefoo || handle_error $?
if you need to actually examine exit statuses. Commented Sep 23, 2017 at 5:29 -
@DavisHerring That's incorrect or misleading in several respects. The shell's
-e
option has some surprising corner cases, and its use is thus even discouraged in some guidelines, especially for beginners. And specifying it on the shebang line is brittle; puttingset -e
in the script itself is more robust.– tripleeeCommented Mar 21, 2021 at 16:55 -
@tripleee: I don’t know that “some guidelines” means that my own advice on the subject is “incorrect”. Is your suggestion to use
set -e
based on runningbash …/foo
and losing the option? If so, there are lots of ways to misrun a script if you choose to run it from the outside… Commented Mar 21, 2021 at 18:39 -
Quite so, but then by definition "the only way" is untrue. For example, the accepted answer to stackoverflow.com/questions/19622198/… summarizes some advice which recommends against
set -e
.– tripleeeCommented Mar 21, 2021 at 18:45
"set -e
" is probably the easiest way to do this. Just put that before any commands in your program.
-
6@SwaroopCH
set -e
your script will abort if any command in your script exit with error status and you didn't handle this error.– AndrewCommented Jan 28, 2013 at 4:43 -
2
set -e
is 100% equivalent toset -o errexit
which unlike the former can be searched. Search for opengroup + errexit for official documentation.– MarcHCommented Nov 15, 2018 at 18:23
If you just call exit in Bash without any parameters, it will return the exit code of the last command. Combined with OR
, Bash should only invoke exit, if the previous command fails. But I haven't tested this.
command1 || exit; command2 || exit;
Bash will also store the exit code of the last command in the variable $?
.
[ $? -eq 0 ] || exit $?; # Exit for nonzero return code
-
4
-
See also Why is testing ”$?” to see if a command succeeded or not, an anti-pattern?– tripleeeCommented Mar 21, 2021 at 16:46
http://cfaj.freeshell.org/shell/cus-faq-2.html#11
How do I get the exit code of
cmd1
incmd1|cmd2
First, note that
cmd1
exit code could be non-zero and still don't mean an error. This happens for instance incmd | head -1
You might observe a 141 (or 269 with ksh93) exit status of
cmd1
, but it's becausecmd
was interrupted by a SIGPIPE signal whenhead -1
terminated after having read one line.To know the exit status of the elements of a pipeline
cmd1 | cmd2 | cmd3
a. with Z shell (
zsh
):The exit codes are provided in the pipestatus special array.
cmd1
exit code is in$pipestatus[1]
,cmd3
exit code in$pipestatus[3]
, so that$?
is always the same as$pipestatus[-1]
.b. with Bash:
The exit codes are provided in the
PIPESTATUS
special array.cmd1
exit code is in${PIPESTATUS[0]}
,cmd3
exit code in${PIPESTATUS[2]}
, so that$?
is always the same as${PIPESTATUS: -1}
....
For more details see Z shell.
-
The first link is broken: "We can’t connect to the server at cfaj.freeshell.org." Commented Feb 14, 2021 at 2:50
For Bash:
# This will trap any errors or commands with non-zero exit status
# by calling function catch_errors()
trap catch_errors ERR;
#
# ... the rest of the script goes here
#
function catch_errors() {
# Do whatever on errors
#
#
echo "script aborted, because of errors";
exit 0;
}
-
21Probably shouldn't "exit 0", since that indicates success. Commented Oct 24, 2010 at 19:14
-
4exit_code=$?;echo "script aborted, because of errors";exit $exit_code– RaSergiyCommented Nov 13, 2012 at 19:54
In Bash this is easy. Just tie them together with &&
:
command1 && command2 && command3
You can also use the nested if construct:
if command1
then
if command2
then
do_something
else
exit
fi
else
exit
fi
-
+1 This was the simplest solution I was looking for. In addition, you can also write
if (! command)
if you expect a nonzero errorcode from command.– BerciCommented Feb 3, 2019 at 23:28 -
this is for sequencial commands.. what if i want to launch those 3 in parallel and kill everyone if any one of them fails? Commented Mar 26, 2020 at 22:28
#
#------------------------------------------------------------------------------
# purpose: to run a command, log cmd output, exit on error
# usage:
# set -e; do_run_cmd_or_exit "$cmd" ; set +e
#------------------------------------------------------------------------------
do_run_cmd_or_exit(){
cmd="$@" ;
do_log "DEBUG running cmd or exit: \"$cmd\""
msg=$($cmd 2>&1)
export exit_code=$?
# If occurred during the execution, exit with error
error_msg="Failed to run the command:
\"$cmd\" with the output:
\"$msg\" !!!"
if [ $exit_code -ne 0 ] ; then
do_log "ERROR $msg"
do_log "FATAL $msg"
do_exit "$exit_code" "$error_msg"
else
# If no errors occurred, just log the message
do_log "DEBUG : cmdoutput : \"$msg\""
fi
}
-
3There's rarely a reason to use
$*
; use"$@"
instead to preserve spaces and wildcards. Commented Sep 23, 2017 at 5:31
$?
after every command. Easy method: putset -e
or#!/bin/bash -e
at the top of your Bash script.