930

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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
samoz
  • 56,849
  • 55
  • 141
  • 195

9 Answers9

1079

Try this statement:

exit 1

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

flying sheep
  • 8,475
  • 5
  • 56
  • 73
Michael Foukarakis
  • 39,737
  • 6
  • 87
  • 123
  • 4
    @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. – Michał Górny May 14 '14 at 08:00
  • 37
    no this closes the window too, not just exit the script – Toni Leigh Mar 21 '18 at 15:37
  • 13
    @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. – Michael Foukarakis Mar 21 '18 at 15:41
  • If I run a bash script with an exit in it, it goes to a login prompt. – Engineer Mar 20 '19 at 20:00
  • 9
    @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.) – Jamie Jan 06 '20 at 20:27
  • 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. – Gagik Apr 23 '21 at 11:09
  • 5
    @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. – rebane2001 Oct 25 '21 at 10:19
808

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.

KitsuneYMG
  • 12,753
  • 4
  • 37
  • 58
Shizzmo
  • 16,231
  • 3
  • 23
  • 15
  • 12
    With `set -e` You still *can* make *some* commands exit with errors without stopping the script: `command 2>&1 || echo $?`. – Adobe Nov 22 '12 at 10:53
  • 1
    I think this will not catch errors if you have commands like `(cd tmp && make)` – Tor Klingberg Feb 07 '13 at 11:34
  • 6
    `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. – Mikko Rantalainen Aug 05 '13 at 12:04
  • 8
    If you're piping commands together, you can also fail if any of them fails by setting the `set -o pipefail` option. – Jake Biesinger Aug 21 '13 at 18:35
  • 21
    Actually the idiomatic code without `set -e` would be just `make || exit $?`. – tripleee Sep 18 '13 at 19:35
  • 5
    Also you have `set -u`. Take a look to the [unofficial **bash strict mode**](http://redsymbol.net/articles/unofficial-bash-strict-mode/): `set -euo pipefail`. – Pablo Bianchi Mar 25 '17 at 18:19
  • 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 $?) – eventhorizon Dec 21 '17 at 15:22
  • @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 Mar 04 '19 at 11:00
  • This answer is valid but should also warn of the many pitfalls `set -e` has - the Bash community is actually quite divided on this, some coders preferring to understand and work around these pitfalls while others prefer avoiding `set -e` at all costs and check things manually instead. I think it would be important to include this, for ex. pointing at [this link](https://mywiki.wooledge.org/BashFAQ/105). – Thomas Guyot-Sionnest Sep 16 '22 at 03:00
313

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.
Jonas Stein
  • 6,826
  • 7
  • 40
  • 72
c.gutierrez
  • 4,740
  • 1
  • 20
  • 14
  • 4
    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. – kaizenCoder May 17 '15 at 09:58
  • 17
    **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](http://stackoverflow.com/questions/9915610/the-difference-between-and). hope that explains everything – flying sheep Aug 07 '15 at 07:16
  • Just curious, but why exit code `111` instead of `1`? Does it have some special meaning I'm not aware of? Searching yielded nothing. – imiric Jul 27 '16 at 17:17
  • @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 Oct 18 '16 at 22:13
  • 1
    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`. – Mark Lakata Oct 18 '16 at 22:14
  • 4
    I can see how I'd use `yell` and `die`. However, `try` not so much. Can you provide an example of you use it? – kshenoy Nov 17 '18 at 19:10
  • 2
    Hmm, but how do you use them in a script? I don't understand what you mean by "try() your statement and code on". – TheJavaGuy-Ivan Milosavljević Mar 18 '20 at 11:58
  • 7
    @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` – Ryan Mar 04 '21 at 20:35
45

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.

kavadias
  • 1,074
  • 10
  • 15
  • 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? – jan Dec 11 '18 at 09:15
  • @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... – kavadias Dec 13 '18 at 17:52
  • 1
    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. – Starman Mar 11 '21 at 22:19
17

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"
Joseph Sheedy
  • 6,296
  • 4
  • 30
  • 31
  • 1
    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? –  Dec 20 '14 at 16:06
  • I don't recall my reason for using eval, the function works fine with cmd_output=$($1) – Joseph Sheedy Dec 21 '14 at 23:12
  • 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. – fuzzygroup Jun 20 '17 at 12:38
  • 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. – Tony-Caffe Jun 13 '18 at 17:48
  • 1
    `eval` is potentially dangerous if you accept arbitrary input, but otherwise this looks pretty nice. – dragon788 Aug 09 '18 at 20:31
  • @dragon788 The function itself should never be passed arbitrary unsanitized input. Does `eval` add extra security implications other than those that are introduced by an alternative solution like `cmd_output=$($1)`? – Joseph Sheedy Aug 28 '23 at 16:09
7

Instead of if construct, you can leverage the short-circuit evaluation:

#!/usr/bin/env bash

echo $[1+1]
echo $[2/0]              # division by 0 but execution of script proceeds
echo $[3+1]
(echo $[4/0]) || exit $? # script halted with code 1 returned from `echo`
echo $[5+1]

Note the pair of parentheses which is necessary because of priority of alternation operator. $? is a special variable set to exit code of most recently called command.

skalee
  • 12,331
  • 6
  • 55
  • 57
  • 1
    if I do `command -that --fails || exit $?` it works without parentheses, what is it about `echo $[4/0]` that causes us to need them? – Anentropic Oct 13 '15 at 09:28
  • 3
    @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 `||`. – bobbogo Mar 04 '19 at 11:09
2

I have the same question but cannot ask it because it would be a duplicate.

The accepted answer, using exit, does not work when the script is a bit more complicated. If you use a background process to check for the condition, exit only exits that process, as it runs in a sub-shell. To kill the script, you have to explicitly kill it (at least that is the only way I know).

Here is a little script on how to do it:

#!/bin/bash

boom() {
    while true; do sleep 1.2; echo boom; done
}

f() {
    echo Hello
    N=0
    while
        ((N++ <10))
    do
        sleep 1
        echo $N
        #        ((N > 5)) && exit 4 # does not work
        ((N > 5)) && { kill -9 $$; exit 5; } # works 
    done
}

boom &
f &

while true; do sleep 0.5; echo beep; done

This is a better answer but still incomplete a I really don't know how to get rid of the boom part.

Gyro Gearloose
  • 1,056
  • 1
  • 9
  • 26
2
#!/bin/bash -x

# exit and report the failure if any command fails
exit_trap () {                                         # ---- (1)
  local lc="$BASH_COMMAND" rc=$?
  echo "Command [$lc] exited with code [$rc]"
}

trap exit_trap EXIT                                    # ---- (2)

set -e                                                 # ---- (3)

Explanation:

This question is also about how to write clean code. Let's divide the above script into multiple parts:


Part - 1: exit_trap is a function that gets called when any step failed and captures the last executed step using $BASH_COMMAND and captures the return code of that step. This is the function that can be used for any clean-up, similar to shutdownhooks

The command currently being executed or about to be executed, unless the shell is executing a command as the result of a trap, in which case it is the command executing at the time of the trap.

Doc.


Part - 2:

trap [action] [signal]

Register the trap action (here exit_trap function) in case of EXIT signal.


Part - 3:

Exit immediately if a sequence of one or more commands returns a non-zero status. The shell does not exit if the command that fails is part of the command list immediately following a while or until keyword, part of the test in an if statement, part of any command executed in a && or || list except the command following the final && or ||, any command in a pipeline but the last, or if the command’s return status is being inverted with !. If a compound command other than a subshell returns a non-zero status because a command failed while -e was being ignored, the shell does not exit. A trap on ERR, if set, is executed before the shell exits.

Doc.


Part - 4:

You can create a common.sh file and source it in all of your scripts.

source common.sh
Vishrant
  • 15,456
  • 11
  • 71
  • 120
1

You can close your program by program name on follow way:

for soft exit do

pkill -9 -x programname # Replace "programmname" by your programme

for hard exit do

pkill -15 -x programname # Replace "programmname" by your programme

If you like to know how to evaluate condition for closing a program, you need to customize your question.

Alfred.37
  • 181
  • 1
  • 12
  • 2
    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. – Soren Bjornstad Sep 16 '21 at 13:28