Do not abort script if a specific command fails - bash

I am running my scripts with:
#!/bin/bash -eu
Which aborts the script whenever a problem occurs, as wanted. But sometimes I expect one of the commands to eventually fail, and I would like to tell bash to ignore the fail condition. In make you can ignore the status of one command with the handy:
-command
Is there something similar in bash? The only thing that comes to mind is the ugly:
set +e
command
set -e

You could just do a no-op on the command failure or set an explicit true condition as
command || true
or for no-op as
command || :
Doing so forces the command-list (even a pipeline) to return an exit status of 0 on failure. See
true | false
echo $?
1
true | false || true
echo $?
0
true | false || :
echo $?
0

Just prepend a ! to the command so that its exit status does not make the script exit when running it with e:
! command
As seen in What's the meaning of a ! before a command in the shell?, having ! command negates the exit status of the given command and, used with set -e, prevents the shell to exit whatever the exit result is on that line.
From Bash Reference Manual → Pipelines:
Each command in a pipeline is executed in its own subshell. The exit status of a pipeline is the exit status of the last command in the pipeline (...). If the reserved word ‘!’ precedes the pipeline, the exit status is the logical negation of the exit status as described above. The shell waits for all commands in the pipeline to terminate before returning a value.
Then we have the info about 4.3.1 The set Builtin:
-e
Exit immediately if a pipeline (see Pipelines), which may consist of a single simple command (see Simple Commands), a list (see Lists), or a compound command (see Compound 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 !.
All together, and quoting my own answer:
When you have:
set -e
! command1
command2
What you are doing is to by-pass the set -e flag in the command1.
Why?
if command1 runs properly, it will return a zero status. ! will negate it, but set -e won't trigger an exit by the because it comes
from a return status inverted with !, as described above.
if command1 fails, it will return a non-zero status. ! will negate it, so the line will end up returning a zero status and the
script will continue normally.

Don't think there is
Could just write your own function though
#!/bin/bash -eu
-(){
set +e
"$#"
set -e
}
- command
echo got here
May want to use a function name since - is already used in bash.
As chepner pointed out it only works for simple commands, though, not pipelines or lists.

Related

How to stop bash function on error when called with shell operators && or ||

I use set -e in bash scripts to stop on error. When I want to handle an error I typically use the || operator (i.e. which git || echo "not found")
However, when I use the || operator after a function call set -e seems to be ignored inside the function.
For example:
#!/bin/bash
set -e
test() {
false
echo "This should not be printed!"
}
test || echo "test failed"
echo "Done"
Desired result is:
test failed
Done
but the actual result is
This should not be printed!
Done
When I just call test the script aborts on the false statement and prints nothing
The only (ugly) workaround I could come up with is to replace the call and error handling with:
set +e
(set -e; test)
if [[ $? -ne 0 ]]; then echo "test failed"; fi
set -e
Is there a better way to get the desired behavior ([Edit] preferably without modifying the function)?
(Tested with git-bash on Windows and GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
If you use set -e you have the be cautionus inside your function just as well as outside.
With set -e the shell basically translates
foo || bar
into
(set +e; foo) || bar
precisely because it needs to see the exit code from the end of the function.
The fix is to be similarly cautious inside the function definition.
# Renamed the function to as to avoid shadowing the test built-in
nst () {
false || return
echo "This should not be printed!"
}
Have a look at the man page of bash. It says about set -e (emphasis by me):
Exit immediately if a pipeline (which may consist of a single simple
command), a list, or a compound command (see SHELL GRAMMAR above), exits
with 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 following the if or elif reserved words, 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 value 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. This option applies to the
shell environment and each subshell environment separately (see COMMAND
EXECUTION ENVIRONMENT above), and may cause subshells to exit before
executing all the commands in the subshell.
If a compound command or shell function executes in a context where -e is
being ignored, none of the commands executed within the compound command or
function body will be affected by the -e setting, even if -e is set and
a command returns a failure status. If a compound command or shell function
sets -e while executing in a context where -e is ignored, that setting will
not have any effect until the compound command or the command containing
the function call completes.
In the following example, the second call to the function test will terminate the script, as it is the last command in a || list.
#!/bin/bash
set -e
test()
{
false
echo "This should not be printed!"
}
test || echo "test failed"
false || test
echo "Will not be printed"

"set -e" and "test" in shell

In shell scripts set -e is often used to make them more robust by stopping the script when some of the commands executed from the script exits with non-zero exit code. I am confused about "set -e" and "test" commands, the result is contrary to what I want.
#!/bin/bash
set -e
a=10
b=9
#test $a -lt $b && false
#test $a -gt $b && false
echo "111"
The real result is:
if a > b, print nothing
if a < b, print 111.
but i don't think so, i think the result is nothing whatever happened.
If you want to exit when a is greater than b, just write
#!/bin/bash
set -e
a=10
b=9
test $a -le $b
echo "111"
If test fails, set -e will cause the script to exit; if it succeeds, the script continues to echo. However, there are many pitfalls to using set -e; I recommend just doing your own error handling.
#!/bin/bash
abort () { printf '%s\n' >&2; exit 1; }
a=10
b=9
test "$a" -le "$b" || abort "$a is greater than $b"
echo "111"
The way you wrote your script means, it will exit without printing anything in all situation BUT if a and b are equals.
If you try with a = 9 and b = 9, you will see the 111 output.
You can easily all the behaviour, using the -x option of GNU/Bash.
The explanation of the exit, is your use of -e option; you can read this in GNU/Bash manual:
Exit immediately if a pipeline (see Pipelines), which may consist of a single simple command (see Simple Commands), a list (see Lists), or a compound command (see Compound 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.
This option applies to the shell environment and each subshell environment separately (see Command Execution Environment), and may cause subshells to exit before executing all the commands in the subshell.
If a compound command or shell function executes in a context where -e is being ignored, none of the commands executed within the compound command or function body will be affected by the -e setting, even if -e is set and a command returns a failure status. If a compound command or shell function sets -e while executing in a context where -e is ignored, that setting will not have any effect until the compound command or the command containing the function call completes.
Thanks everyone, i find the answer which is
SHELL Exit immediately if a pipeline (which may consist of a single simple com-mand), a list, or a compound command (see SHELL GRAMMAR above), exits with 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 following the if or elif reserved words, 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 value is being inverted with !.

Script continues if error occurs in && and set -e is used

If in a script I use set -e the script continues after an error has occurred in a statement that executes two commands with &&.
For example:
set -e
cd nonexistingdirectory && echo "&&"
echo continue
This gives the following output:
./exit.sh: line 3: cd: nonexistingdirectory: No such file or directory
continue
I want the script to exit after cd nonexistingdirectory and stop.
How can I do this?
** Edit**
I have multiple scripts using && that I need to fix to make sure they exit upon error. But I want a minimum impact/risk solution. I will try the solution mentioned in the comments to replace && with ; combined with set -e.
This is by design. If you use &&, bash assumes you want to handle errors yourself, so it doesn't abort on failure in the first command.
Possible fix:
set -e
cd nonexistingdirectory
echo "&&"
echo continue
Now there are only two possibilities:
cd succeeds and the script continues as usual.
cd fails and bash aborts execution because of set -e.
The problem here is your && command.
In fact, when a command that retrieves error is executed together (&&) with another command, the set -e doesn't work.
If you explain better the real use case we could find out a work around that fits your needs.
set [+abefhkmnptuvxBCEHPT] [+o option] [arg ...]
-e Exit immediately if a pipeline (which may consist of a single simple command), a subshell command enclosed in parentheses, or
one of the commands executed as part of a command list enclosed by braces (see SHELL GRAMMAR above) exits with 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 following the if or elif reserved words, part of any command executed in a && or || list except the command fol-
lowing the final && or ||, any command in a pipeline but the last, or if the command's return value is being inverted with !.
from man bash

Why does bash eval return zero when backgrounded command fails?

Editing this post, original is at bottom beneath the "Thanks!"
command='a.out arg1 arg2 &'
eval ${command}
if [ $? -ne 0 ]; then
printf "Command \'${command}\' failed\n"
exit 1
fi
wait
Here is a test script that demonstrates the problem, which I oversimplified
in the original post. Notice the ampersand in line 2 and the wait command.
These more faithfully represent my script. In case it matters, the ampersand
is sometimes there and sometimes not, its presence is determined by a user-
specified flag that indicates whether or not to background a long arithmetic
calculation. And, also in case it matters, I'm actually backgrounding many
(twelve) processes, i.e., ${command[0..11]}. I want the script to die if any
fail. I use 'wait' to synchronize the successful return of all processes.
Happy (sort of) to use another approach but this almost works.
The ampersand (for backgrounding) seems to cause the problem.
When ${command} omits the ampersand, the script runs as expected:
The executable a.out is not found, a complaint to that effect is issued,
and $? is non-zero so the host script exits. When ${command} includes
the ampersand, the same complaint is issued but $? is zero so the
script continues to execute. I want the script to die immediately when
a.out fails but how do I obtain the non-zero return value from a
backgrounded process?
Thanks!
(original post):
I have a bash script that uses commands of the form
eval ${command}
if [ $? -ne 0 ]; then
printf "Command ${command} failed"
exit 1
fi
where ${command} is a string of words, e.g., "a.out arg1 ... argn".
The problem is that the return code from eval (i.e., $?) is always
zero even when ${command} fails. Removing the "eval" from the above
snippet allows the correct return code ($?) to be returned and thus
halt the script. I need to keep my command string in a variable
(${command}) in order to manipulate it elsewhere, and simply running
${command} without the eval doesn't work well for other reasons. How do I catch the
correct return code when using eval?
Thanks!
Charlie
The ampersand (for backgrounding) seems to cause the problem.
That is correct.
The shell cannot know a command's exit code until the command completes. When you put a command in background, the shell does not wait for completion. Hence, it cannot know the (future) return status of the command in background.
This is documented in man bash:
If a command is terminated by the control operator &, the shell
executes the command in the background in a subshell. The shell does
not wait for the command to finish, and the return status is 0.
In other words, the return code after putting a command in background is always 0 because the shell cannot predict the future return code of a command that has not yet completed.
If you want to find the return status of commands in the background, you need to use the wait command.
Examples
The command false always sets a return status of 1:
$ false ; echo status=$?
status=1
Observe, though, what happens if we background it:
$ false & echo status=$?
[1] 4051
status=0
The status is 0 because the command was put in background and the shell cannot predict its future exit code. If we wait a few moments, we will see:
$
[1]+ Exit 1 false
Here, the shell is notifying us that the brackground task completed and its return status was just as it should be: 1.
In the above, we did not use eval. If we do, nothing changes:
$ eval 'false &' ; echo status=$?
[1] 4094
status=0
$
[1]+ Exit 1 false
If you do want the return status of a backgrounded command, use wait. For example, this shows how to capture the return status of false:
$ false & wait $!; echo status=$?
[1] 4613
[1]+ Exit 1 false
status=1
From the man page on my system:
eval [arg ...] The args are read and concatenated together into a single command. This command is then read and executed by the shell, and its exit status is returned as the value of eval. If there are no args, or only null arguments, eval returns 0.
If your system documentation is 'the same', then, most likely, whatever commands you are running are the problem, i.e. 'a.out' is returning '0' on exit instead of a non-zero value. Add appropriate 'exit return code' to your compiled program.
You might also try using $() which will 'run' your binary instead of 'evaluating' it..., i.e.
STATUS=$(a.out var var var)
As long on only one 'command' is in the stream, then the value of $? is the 'exit code'; otherwise, $? is the return code for the last command in a multi-command 'pipe'...
:)
Dale

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?
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.
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.
$# is all arguments again, but different.
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"
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.
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.
#!/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

Resources