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
Related
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 !.
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.
There are recommendations to use the following options to make Bash fail fast:
set -o errexit
set -o nounset
set -o pipefail
These options however do not work as expected for Bash functions, piped via ||.
E.g. in script
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
my_function() {
test -z "$1"
echo "this SHOULD NOT be printed"
}
my_function 1 || echo "???" # 1
my_function 1 # 2
echo "this will not be printed"
Line # 2 will cause the script to terminate with code 1 without any output. This is what I expect.
Line # 1 confuses me actually: my_function will successfully be completed, printing "this SHOULD NOT be printed" and returning code 0, thus the "???" will not be printed.
How can I make Bash to process my_function on line # 1 the same fail fast way, as on line # 2?
There are also better recommendations not to use set -e/errexit:
Clearly we don't want to abort when the conditional (in if [ -d /foo ]; then), returns non-zero. [...] The implementors decided to make a bunch of special rules, like "commands that are part of an if test are immune", or "commands in a pipeline, other than the last one, are immune".
These rules are extremely convoluted, and they still fail to catch even some remarkably simple cases. Even worse, the rules change from one Bash version to another, as Bash attempts to track the extremely slippery POSIX definition of this "feature". When a SubShell is involved, it gets worse still -- the behavior changes depending on whether Bash is invoked in POSIX mode.
If the script were to stop on a non-zero return, foo || bar would also be useless because bar would never run (the script would instead exit). Therefore, one of these convoluted rules is that non-zero returns in commands on the left-hand side of && or || will not cause the script to exit. This is the effect you're seeing.
There is no way to make set -e work properly and there is no automatic replacement. You can not make it work the way you want without writing manual error handling.
Looking at the bash manual, here is what is says for the trap command:
trap [-lp] [arg] [sigspec …]
If a sigspec is ERR, the command arg is executed whenever a pipeline (which may consist of a single simple command), a list, or a compound
command returns a non-zero exit status, subject to the following
conditions. The ERR trap is not executed if the failed command is part
of the command list immediately following an until or while keyword,
part of the test following the if or elif reserved words, part of a
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 using !. These are the same
conditions obeyed by the errexit (-e) option.
Look at the last sentence especially. That explains it!
This question already has answers here:
What does set -e mean in a bash script?
(10 answers)
Closed 6 years ago.
In a bash script what is the use of
set -e
?
I expect it has something to do with environment variables but I have not come across it before
quoting from help set
-e Exit immediately if a command exits with a non-zero status.
i.e the script or shell would exit as soon as it encounters any command that exited with a non-0(failure) exit code.
Any command that fails would result in the shell exiting immediately.
As an example:
Open up a terminal and type the following:
$ set -e
$ grep abcd <<< "abc"
As soon you hit enter after grep command, the shell exits because grep exited with a non-0 status i.e it couldn't find regex abcd in text abc
Note: to unset this behavior use set +e.
man bash says
Exit immediately if a simple 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 in an if statement,
part of a && or ││ list, or if the command’s return value is being inverted via !. A
trap on ERR, if set, is executed before the shell exits.
It is super convenient way to get "fail-fast" behaviour if you want to avoid testing the return code of every command in a bash script.
Suppose there is no file named trumpet in the current directory below script :
#!/bin/bash
# demonstrates set -e
# set -e means exit immediately if a command exited with a non zero status
set -e
ls trumpet #no such file so $? is non-zero, hence the script aborts here
# you still get ls: cannot access trumpet: No such file or directory
echo "some other stuff" # will never be executed.
You may also combine the e with the x option like set -ex where :
-x Print commands and their arguments as they are executed.
This may help you debugging bash scripts.
Reference:Set Manpage
Let's imagine I have a bash script, where I call this:
bash -c "some_command"
do something with code of some_command here
Is it possible to obtain the code of some_command? I'm not executing some_command directly in the shell running the script because I don't want to alter it's environment.
$? will contain the return code of some_command just as usual.
Of course it might also contain a code from bash, in case something went wrong before your command could even be executed (wrong filename, whatnot).
Here's an illustration of $? and the parenthesis subshell mentioned by Paggas and Matti:
$ (exit a); echo $?
-bash: exit: a: numeric argument required
255
$ (exit 33); echo $?
33
In the first case, the code is a Bash error and in the second case it's the exit code of exit.
You can use the $? variable, check out the bash documentation for this, it stores the exit status of the last command.
Also, you might want to check out the bracket-style command blocks of bash (e.g. comm1 && (comm2 || comm3) && comm4), they are always executed in a subshell thus not altering the current environment, and are more powerful as well!
EDIT: For instance, when using ()-style blocks as compared to bash -c 'command', you don't have to worry about escaping any argument strings with spaces, or any other special shell syntax. You directly use the shell syntax, it's a normal part of the rest of the code.