In a Bash script, how can I exit the entire script if a certain condition occurs?

854,641

Solution 1

Try this statement:

exit 1

Replace 1 with appropriate error codes. See also Exit Codes With Special Meanings.

Solution 2

Use set -e

#!/bin/bash

set -e

/bin/command-that-fails
/bin/command-that-fails2

The script will terminate after the first line that fails (returns nonzero exit code). In this case, command-that-fails2 will not run.

If you were to check the return status of every single command, your script would look like this:

#!/bin/bash

# I'm assuming you're using make

cd /project-dir
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

cd /project-dir2
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

With set -e it would look like:

#!/bin/bash

set -e

cd /project-dir
make

cd /project-dir2
make

Any command that fails will cause the entire script to fail and return an exit status you can check with $?. If your script is very long or you're building a lot of stuff it's going to get pretty ugly if you add return status checks everywhere.

Solution 3

A SysOps guy once taught me the Three-Fingered Claw technique:

yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "cannot $*"; }

These functions are *NIX OS and shell flavor-robust. Put them at the beginning of your script (bash or otherwise), try() your statement and code on.

Explanation

(based on flying sheep comment).

  • yell: print the script name and all arguments to stderr:
    • $0 is the path to the script ;
    • $* are all arguments.
    • >&2 means > redirect stdout to & pipe 2. pipe 1 would be stdout itself.
  • die does the same as yell, but exits with a non-0 exit status, which means “fail”.
  • try uses the || (boolean OR), which only evaluates the right side if the left one failed.

Solution 4

If you will invoke the script with source, you can use return <x> where <x> will be the script exit status (use a non-zero value for error or false). But if you invoke an executable script (i.e., directly with its filename), the return statement will result in a complain (error message "return: can only `return' from a function or sourced script").

If exit <x> is used instead, when the script is invoked with source, it will result in exiting the shell that started the script, but an executable script will just terminate, as expected.

To handle either case in the same script, you can use

return <x> 2> /dev/null || exit <x>

This will handle whichever invocation may be suitable. That is assuming you will use this statement at the script's top level. I would advise against directly exiting the script from within a function.

Note: <x> is supposed to be just a number.

Solution 5

I often include a function called run() to handle errors. Every call I want to make is passed to this function so the entire script exits when a failure is hit. The advantage of this over the set -e solution is that the script doesn't exit silently when a line fails, and can tell you what the problem is. In the following example, the 3rd line is not executed because the script exits at the call to false.

function run() {
  cmd_output=$(eval $1)
  return_value=$?
  if [ $return_value != 0 ]; then
    echo "Command $1 failed"
    exit -1
  else
    echo "output: $cmd_output"
    echo "Command succeeded."
  fi
  return $return_value
}
run "date"
run "false"
run "date"
Share:
854,641
samoz
Author by

samoz

I am a cryptographer, working in the West Lafayete, IN area. My interests include reverse engineering, information security, ellipic curves, trusted computing environments, operating systems, and embedded systems. Most of my reversing is on Windows, but I like to practice my make-fu and Unix-fu on Arch Linux.

Updated on July 10, 2022

Comments

  • samoz
    samoz almost 2 years

    I'm writing a script in Bash to test some code. However, it seems silly to run the tests if compiling the code fails in the first place, in which case I'll just abort the tests.

    Is there a way I can do this without wrapping the entire script inside of a while loop and using breaks? Something like a dun dun dun goto?

  • Adobe
    Adobe over 11 years
    With set -e You still can make some commands exit with errors without stopping the script: command 2>&1 || echo $?.
  • Tor Klingberg
    Tor Klingberg over 11 years
    I think this will not catch errors if you have commands like (cd tmp && make)
  • Mikko Rantalainen
    Mikko Rantalainen almost 11 years
    set -e will abort the script if a pipeline or command structure returns non-zero value. For example foo || bar will fail only if both foo and bar return non-zero value. Usually a well written bash script will work if you add set -e at the start and the addition works as an automated sanity check: abort the script if anything goes wrong.
  • Jake Biesinger
    Jake Biesinger over 10 years
    If you're piping commands together, you can also fail if any of them fails by setting the set -o pipefail option.
  • tripleee
    tripleee over 10 years
    Actually the idiomatic code without set -e would be just make || exit $?.
  • Michał Górny
    Michał Górny about 10 years
    @CMCDragonkai, usually any non-zero code will work. If you don't need anything special, you can just use 1 consistently. If the script is meant to be run by another script, you may want to define your own set of status code with particular meaning. For example, 1 == tests failed, 2 == compilation failed. If the script is part of something else, you may need to adjust the codes to match the practices used there. For example, when part of test suite run by automake, the code 77 is used to mark a test skipped.
  • Admin
    Admin over 9 years
    Man, for some reason, I really like this answer. I recognize it's a bit more complicated, but it seems so useful. And given that I'm no bash expert, it leads me to believe that my logic is faulty, and there's something wrong with this methodology, otherwise, I feel others would have given it more praise. So, what's the problem with this function? Is there anything I should be looking out for here?
  • Joseph Sheedy
    Joseph Sheedy over 9 years
    I don't recall my reason for using eval, the function works fine with cmd_output=$($1)
  • kaizenCoder
    kaizenCoder almost 9 years
    I'm fairly new to unix scripting. Can you please explain how the above functions are executed? I'm seeing some new syntax that I'm not familiar with. Thanks.
  • flying sheep
    flying sheep almost 9 years
    yell: $0 is the path to the script. $* are all arguments. >&2 means “> redirect stdout to & pipe 2”. pipe 1 would be stdout itself. so yell prefixes all arguments with the script name and prints to stderr. die does the same as yell, but exits with a non-0 exit status, which means “fail”. try uses the boolean or ||, which only evaluates the right side if the left one didn’t fail. $@ is all arguments again, but different. hope that explains everything
  • Anentropic
    Anentropic over 8 years
    if I do command -that --fails || exit $? it works without parentheses, what is it about echo $[4/0] that causes us to need them?
  • imiric
    imiric almost 8 years
    Just curious, but why exit code 111 instead of 1? Does it have some special meaning I'm not aware of? Searching yielded nothing.
  • Mark Lakata
    Mark Lakata over 7 years
    @imiric -I think exit code 111 for no specific reason, but the conventions are that success is 0, and small integers are normal failures, like 1,2,3. The exit code is limited to 8 bits, but there is ambiguity as to whether it is signed or not, so most people stick with 0-127 to be safe. So 111 is a big number, far from the other end, that means "exceptional" failure.
  • Mark Lakata
    Mark Lakata over 7 years
    I modified this to be die() { yell "$1"; exit $2; } so that you can pass a message and exit code with die "divide by zero" 115.
  • Pablo Bianchi
    Pablo Bianchi about 7 years
    Also you have set -u. Take a look to the unofficial bash strict mode: set -euo pipefail.
  • fuzzygroup
    fuzzygroup almost 7 years
    I just implemented this as part of a complex deploy process and it worked fantastic. Thank you and here's a comment and an upvote.
  • eventhorizon
    eventhorizon over 6 years
    Keep in mind that subscripts (scripts in their own bash "instance") like (command) need some help to return a failure like (set -e; command) or (command || exit $?)
  • Toni Leigh
    Toni Leigh about 6 years
    no this closes the window too, not just exit the script
  • Michael Foukarakis
    Michael Foukarakis about 6 years
    @ToniLeigh bash does not have the concept of a "window", you are probably confused with regard to what your particular set-up - e.g. a terminal emulator - does.
  • Tony-Caffe
    Tony-Caffe almost 6 years
    Truly amazing work! This is the simplest and cleanest solution that works well. For me I added this before a command in a FOR loop since FOR loops will not pick up the set -e option. Then the command, since it is with arguments, I used single quotes to avoid bash issues like so: runTry 'mysqldump $DB_PASS --user="$DB_USER" --host="$BV_DB_HOST" --triggers --routines --events --single-transaction --verbose $DB_SCHEMA $tables -r $BACKUP_DIR/$tables$BACKUP_FILE_NAME'. Note I changed the name of the function to runTry.
  • dragon788
    dragon788 almost 6 years
    eval is potentially dangerous if you accept arbitrary input, but otherwise this looks pretty nice.
  • kshenoy
    kshenoy over 5 years
    I can see how I'd use yell and die. However, try not so much. Can you provide an example of you use it?
  • jan
    jan over 5 years
    Doesn't work for me in a script with an return/exit inside a function, i.e. is it possible to exit inside the function without existing the shell, but without making the function caller care for the correct check of the return code of the function?
  • kavadias
    kavadias over 5 years
    @jan What the caller does (or does not do) with return values, is completely orthogonal (i.e., independent of) how you return from the function (... w/o exiting the shell, no matter what the invocation). It mainly depends on the caller code, which is not part of this Q&A. You could even tailor the return value of the function to the needs of the caller, but this answer does not limit what this return value can be...
  • bobbogo
    bobbogo about 5 years
    @TorKlingberg The exit code of (echo hello; make) is the exit code of the make. A non-zero exit will therefore be caught under -e
  • bobbogo
    bobbogo about 5 years
    @Anentropic @skalee The parentheses have nothing to do with precedence, but exception handling. A divide-by-zero will cause an immediate exit from the shell with code 1. Without the parentheses (i.e., a simple echo $[4/0] || exit $?) bash will never execute the echo, let alone obey the ||.
  • Engineer
    Engineer about 5 years
    If I run a bash script with an exit in it, it goes to a login prompt.
  • Sergey Kotyushkin
    Sergey Kotyushkin over 4 years
    @ToniLeigh If it's closing the "window" likely you're putting the exit # command inside a function, not a script. (In which case use return # instead.)
  • TheJavaGuy-Ivan Milosavljević
    TheJavaGuy-Ivan Milosavljević about 4 years
    Hmm, but how do you use them in a script? I don't understand what you mean by "try() your statement and code on".
  • Ryan
    Ryan about 3 years
    @TheJavaGuy-IvanMilosavljević once you've added those three lines to the top of your script, you'd start using the try function with your code. Ex: try cp ./fake.txt ./copy.txt
  • Starman
    Starman about 3 years
    This should be the accepted answer. The Q did not specify if it was using "source script.sh" or "./script.sh". People use both, frequently. The currently accepted answer does not work if you use source. This answer works for both cases.
  • Gagik
    Gagik about 3 years
    For short scripts read -r command can also be an alternative to exit if only script is always being executed in front of the terminal user, in order to give user choice to terminate the execution or continue with error.
  • Soren Bjornstad
    Soren Bjornstad over 2 years
    You have 9 and 15 mixed up. Signal 9 is SIGKILL, which is a “harder” exit that forces immediate termination. Signal 15 is SIGTERM, which politely asks the program to terminate and lets it clean up if it wants.
  • rebane2001
    rebane2001 over 2 years
    @ToniLeigh The exit command only exits the bash process the script is running in. If you run your script with ./script.sh or bash script.sh, your "window" or shell will stay open, because a new bash process is created for the script. On the other hand, if you run your script with . script.sh or source script.sh, it executes in your current bash instance and exits it instead.