bash script using && not stopping on error - 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.

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

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."

&& 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

You're using the wrong operator. What you need is || (example 2 below).
Explanation:
Note:
`A && B` # => Run `A`, and then `B` if A ran successfully.
`A || B` # => Run `A`, and then `B` if A did not run successfully.

Related

A way to ignore exit status in gitlab job pipeline [duplicate]

In our project we have a shell script which is to be sourced to set up environment variables for the subsequent build process or to run the built applications.
It contains a block which checks the already set variables and does some adjustment.
# part of setup.sh
for LIBRARY in "${LIBRARIES_WE_NEED[#]}"
do
echo $LD_LIBRARY_PATH | \grep $LIBRARY > /dev/null
if [ $? -ne 0 ]
then
echo Adding $LIBRARY
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$LIBRARY
else
echo Not adding $LIBRARY
fi
done
i.e. it checks if a path to a library is already in $LD_LIBRARY_PATH and if not, adds it.
(To be fair, this could be written differently (like here), but assume the script is supposed to achieve something which is very hard to do without calling a program, checking $? and then either doing one thing or doing another thing).
The .gitlab-ci.yml then contains
before_script:
- yum install -y <various packages>
- source setup.sh
but the runner decides to stop the before script the very moment $? is non-zero, i.e. when the if-statement decides to add a path to $LD_LIBRARY_PATH.
Now it is nice that the gitlab runner checks $? after each line of my script, but here it'd be great if the lines in .gitlab-ci.yml were considered atomic.
Is there a way to avoid the intermediate checks of $? in a script that's sourced in .gitlab-ci.yml?
Use command_that_might_fail || true to mask the exit status of said command.
Also note that you can use grep -q to prevent output:
echo "$LD_LIBRARY_PATH" | grep -q "$LIBRARY" || true
This will however also mask $? which you might not want. If you want to check if the command exits correct you might use:
if echo "$LD_LIBRARY_PATH" | grep -q "$LIBRARY"; then
echo "Adding $LIBRARY"
else
...
fi
I suspect that gitlab-ci sets -e which you can disabled with set +e:
set +e # Disable exit on error
for library in "${LIBRARIES_WE_NEED[#]}"; do
...
done
set -e # Enable exit on error
Future reading: Why double quotes matter and Pitfalls with set -e
Another trick that I am using is a special kind of "|| true", combined with having access to previous exit code.
- exit_code=0
- ./myScript.sh || exit_code=$?
- if [ ${exit_code} -ne 0 ]; then echo "It failed!" ; else echo "It worked!"; fi
The $exit_code=$? always evaluates to "true" so you get a non failing command but you also receive exit_code and you can do whatever you want with it.
Note please, that you shouldn't skip the first line or exit_code will be uninitialized (since on successful run of script, the or'ed part is never executed and the if ends up being)
if [ -ne 0 ];
instead of
if [ 0 -ne 0 ];
Which causes syntax error.

Makefile check if a block of commands succeeded

I have a target such as this:
.PHONY: do-symlink
do-symlink:
ln -s $(HERE_ONE_FILE) $(THERE_ONE_DIR)/
ln -s $(HERE_TWO_FILE) $(THERE_ONE_DIR)/
# check if those succeed or failed. I want to make sure both have passed
if succeeded:
do_something
else:
do_something_else
How can i check that they are both succeed? and if so, do something based on it?
So first of all, if a make recipe line fails, make fails, and stops executing the recipe. So if you put a recipe line at the end #echo "succeeded", then this will on run if all previous lines worked. As far as printing if something specific if one fails, you can use bash || for that
all:
#command1 || { echo "command1 failed"; exit 1 }
#command2 || { echo "command2 failed"; exit 1 }
#echo "command1 and command2 passed"
Notice the exit 1's in there. Normally, false || echo "false" would have a return status of 0 (pass), because the exit status is taken from the last command run (echo "false"), which will always succeed. This would cause make to always continue running the next recipe line. You may want this, however, you can preserve the failure by compounding the statement and doing an exit 1 at the end.
For running both commands regardless of exit status, and then handling the exits after, prefix the recipe lines with -. This will cause make to not stop running if the command fails. Notice however that each recipe line is run in its own shell, so one recipe line cannot directly access the return code from another line. You could output to a file and then access that file in a later recipe line:
all:
-#command1; echo $? > .commands.result
-#command2; echo $? >> .commands.result
#if grep -q "^00$" .commands.result; then \
echo both commands passed; \
else \
echo "at least one command failed"
(or you could always concatenate all the recipes lines into a single bash command and pass the variables around as well).

How to trigger a fail in Travis CI?

One of my test is a simple bash command with an if condition. I want Travis CI to consider a build as failed if the condition is positive.
I try to do it this way (a part of the .travis.yml file):
# ...
script:
- npm run build
- if [[ `git status --porcelain` ]]; then >&2 echo "Fail"; fi
# ...
But when the condition is positive, the message is just printed and the build is considered as successful.
What should I do to make a build failed when the condition is positive?
Just add exit 1; after the echo. More info.
If you just want to assert a condition but continue with testing, the following worked for me:
bash -c 'if [[ `git status --porcelain` ]]; then >&2 echo "Fail"; exit 1; fi'
This will make the build results fail but not terminate it.
Add return 1;
Travis compiles the different commands into a single bash script so exit 1 or travis_terminate 1 will abruptly interrupt the workflow and skip the after_script phase.
For complex commands that you want to make more readable and don't want to move to their own script, you can take advantage of YAML's literal scalar indicator:
script:
- |
if [[ `git status --porcelain` ]]; then
>&2 echo "Fail"
return 1
fi

Continue make command even if one operation within fails [duplicate]

This question already has answers here:
Make: how to continue after a command fails?
(8 answers)
Closed 6 years ago.
I'm looking for a way to continue a make command in the event of a error failure.
I need a way of wrapping a command so it doesn't respond with a exit code 1.
test:
exit 1 ;\
echo 'hi' ;\
I need a way to wrap something like this:
example:
somecommand && othercommand ;\
echo 'hi' ;\
Where somecommand can exit with a 1 (error) and not run othercommand or a 0 which would run othercommand.
This should do:
test:
commandThatMayFail && otherCommand || true
echo hi
You can try it like this:
test:
rm fileDoesNotExist && echo foo || true
echo bar
You can also use make -i ... to ignore all errors. Per the man page:
-i, --ignore-errors
Ignore all errors in commands executed to remake files.
Prefixing a recipe with - tells make to ignore any errors returned by that line, the only other thing you need to do is run the two recipes separately.
example:
-somecommand && othercommand
echo 'hi'

Why doesn't my if statement with backticks work properly?

I am trying to make a Bash script where the user will be able to copy a file, and see if it was successfully done or not. But every time the copy is done, properly or not, the second output "copy was not done" is shown. Any idea how to solve this?
if [ `cp -i $files $destination` ];then
echo "Copy successful."
else
echo "Copy was not done"
fi
What you want is
if cp -i "$file" "$destination"; then #...
Don't forget the quotes.
You version:
if [ `cp -i $files $destination` ];then #..
will always execute the else branch.
The if statement in the shell takes a command.
If that command succeeds (returns 0, which gets assigned into $?), then the condition succeeds.
If you do if [ ... ]; then, then it's the same as
if test ... ; then because [ ] is syntactic sugar for the test command/builtin.
In your case, you're passing the result of the stdout* of the cp operation as an argument to test
The stdout of a cp operation will be empty (cp generally only outputs errors and those go to stderr). A test invocation with an empty argument list is an error. The error results in a nonzero exit status and thus you always get the else branch.
*the $() process substitution or the backtick process substitution slurp the stdout of the command they run
With back ticks you are testing the output of the cp command, not its status. You also don't need the test command (square brackets) here.
Just use:
if cp ... ; then
...
In addition to testing the output verses status as correctly pointed out in the other answer, you can make use of a compound command to do exactly what your are attempting, without requiring the full if ... then ... else ... fi syntax. For example:
cp -i "$files" "$destination" && echo "Copy successful." || echo "Copy was not done"
Which essentially does the exact same thing as the if syntax. Basically:
command && 'next cmd if 1st succeeded'
and
command || 'next cmd if 1st failed'
You are simply using command && 'next cmd if 1st succeeded' as the command in command || 'next cmd if 1st failed'. Together it is simply:
command && 'next cmd if 1st succeeded' || 'next cmd if 1st failed'
Note: make sure to always quote your variables to prevent word-splitting, and pathname expansion, etc...
Try:
cp -i $files $destination
#check return value $? if cp command was successful
if [ "$?" == "0" ];then
echo "Copy successful."
else
echo "Copy was not done"
fi

Resources