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?
Try this statement:
exit 1
Replace 1
with appropriate error codes. See also Exit Codes With Special Meanings.
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.
set -e
You still can make some commands exit with errors without stopping the script: command 2>&1 || echo $?
.
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.
set -o pipefail
option.
set -e
would be just make || exit $?
.
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.
$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. $@ is all arguments again, but different.
$@ is all arguments again, but different.
$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
yell
and die
. However, try
not so much. Can you provide an example of you use it?
try
function with your code. Ex: try cp ./fake.txt ./copy.txt
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.
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"
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.
eval
is potentially dangerous if you accept arbitrary input, but otherwise this looks pretty nice.
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.
command -that --fails || exit $?
it works without parentheses, what is it about echo $[4/0]
that causes us to need them?
echo $[4/0] || exit $?
) bash will never execute the echo
, let alone obey the ||
.
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.
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.
Success story sharing
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 code77
is used to mark a test skipped.exit #
command inside a function, not a script. (In which case usereturn #
instead.)exit
command only exits the bash process the script is running in. If you run your script with./script.sh
orbash 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
orsource script.sh
, it executes in your current bash instance and exits it instead.