bash script using && not stopping on error

时间:2017-08-04 12:17:17

标签: bash

i have a script that I accidentally ran without an underlying file present, and my script doesn't have a check for this file, because the script should stop when the command that requires that file exits 1.

i got caught out because it went ahead and skipped the sleep command and the ||exit 0 if test that I have as some protection protection. i would really like to know why. the if test and exit works if the preceding command doesn't fail.

if i strip the script down I can see some unexpected behaviour where the script doesn't stop at the && and skips the next sleep command.

is this not the correct way to use &&?

you can test this here:

#!/bin/bash
mkdir /root/simulatecomplexcommandthatreturns1 &&
sleep 5m
echo "let's go ahead and delete all the stuff"
find /blah/ -delete

this is on debian 9

EDIT:

for clarity, I want the script to stop when it encounters an error and I have &&. I just thought it was odd that it didn't run the sleep command.

4 个答案:

答案 0 :(得分:3)

The && only apply to next command, for a sequence, braces must be added:

#!/bin/bash
mkdir /root/simulatecomplexcommandthatreturns1 && {
    sleep 5m
    echo "let's go ahead and delete all the stuff"
    find /blah/ -delete
}

or to avoid indent level the condition can be inverted

#!/bin/bash
mkdir /root/simulatecomplexcommandthatreturns1 || {
    echo "something goes wrong"
    exit 1
}

# ok, continue
sleep 5m
echo "let's go ahead and delete all the stuff"
find /blah/ -delete

答案 1 :(得分:1)

If you want a script to abort/exit as soon as a command pipeline exists with a non-zero status (that means the last command in the pipeline, unless pipefail enabled), you might consider using:

set -e

In your example:

#!/bin/bash
set -e
mkdir /root/simulatecomplexcommandthatreturns1
sleep 5m
echo "let's go ahead and delete all the stuff"
find /blah/ -delete

when any of the commands fails, your script will exit.

Note however, this can sometimes lead to unwanted exits. For example it's normal for grep to exit with error if no match was found (you might "silence" such commands with grep .. || true ensuring the pipeline exits with success).

You'll probably be safer with manually testing for failure. For example:

if ! mkdir /root/simulatecomplexcommandthatreturns1; then
    echo "Error description."
    exit 1
fi

The usage of shortcircuiting && and || is best reserved for simple command sequences, when the execution of the next depends on successful exit of the previous. For example, the command pipeline:

mkdir /somedir && cp file /somedir && touch /somedir/file

will try to create a directory, if created successfully, it will try to copy the file; and if the file was copied successfully, it will touch the file.

Example with OR:

cp file /somedir || exit 1

where we try to copy the file and we exit if copy failed.

But you should be very careful when combining the two, since the result can be unexpected. For example:

a && b || c

is not equal to:

if a; then b; else c; fi

because c in the former expression will get executed whenever either of a or b fails (exits with a non-zero status). In the latter expression, c is executed only if a fails. For example:

true && false || echo "This also gets executed."

答案 2 :(得分:0)

You're using the wrong operator. What you need is || (example 2 below).

Explanation:

Note:

`A && B` # => Run `A`, and then `B` iff A ran successfully.

`A || B` # => Run `A`, and then `B` iff A did not run successfully.

答案 3 :(得分:0)

&& is like the AND operator with the property :

fail       &&   <anything>        equals fail
<anything> &&   fail              equals fail
success    &&   success           equals success

So, if the first operand (or command) fails, there is no point in resolving the second command.

Hence, when mkdir /root/simulatecomplexcommandthatreturns1 fails in

mkdir /root/simulatecomplexcommandthatreturns1 &&
sleep 5m

It skips the second command.

What you want here is || or the OR operator

fail    ||   fail         equals fail
fail    ||   success      equals success
success ||   <anything>   equals success

So, using if the mkdir /root/simulatecomplexcommandthatreturns1 fails in"

mkdir /root/simulatecomplexcommandthatreturns1 || sleep 5m

It will have to evaluate the second operand ie the sleep 5m command.

EDIT :

Note that bash script do not exit if one of its command fails. It only exits when it reaches the end of the script or when exit is called.

if you want to exit when a certain command fails, you would do something like :

$ theCommandThatCanFail || exit 1  # the first command returns fail and
                                   # since its `OR` operator, the second
                                   # command will be resolved