ChatGPT解决这个技术问题 Extra ChatGPT

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

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?


f
flying sheep

Try this statement:

exit 1

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


@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.
no this closes the window too, not just exit the script
@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.
@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.)
@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.
K
KitsuneYMG

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.


With 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.
If you're piping commands together, you can also fail if any of them fails by setting the set -o pipefail option.
Actually the idiomatic code without set -e would be just make || exit $?.
Also you have set -u. Take a look to the unofficial bash strict mode: set -euo pipefail.
J
Jonas Stein

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.


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.
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
I can see how I'd use yell and die. However, try not so much. Can you provide an example of you use it?
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-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
k
kavadias

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.


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 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...
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.
J
Joseph Sheedy

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"

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?
I don't recall my reason for using eval, the function works fine with cmd_output=$($1)
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.
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.
eval is potentially dangerous if you accept arbitrary input, but otherwise this looks pretty nice.
s
skalee

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.


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 @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 ||.
G
Gyro Gearloose

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.


E
Eddy763

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.


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.