This question already has answers here:
How does AND and OR operators work in Bash?
(6 answers)
Closed 2 years ago.
When I execute this in the following line in my shell
echo this || echo that && echo other
The output is:
this
other
Now I don't understand how echo other is executed, because echo that does not return a successful exit status because it is not executed. The && operator only executes the right command when the left command exits successfully.
Does this mean the && operator will execute the righthand command if anything on the left side exits successfully?
You can rewrite that example as:
(echo this || echo that) && echo other
You say echo that does not return a successful exit status, but rather, it doesn't return anything at all - it is not executed. So the expression (echo this || echo that) has a successful result (the return of echo this), which makes echo other be executed.
A good example for this situation is just running echo this || echo that - it has a return value of 0, as such, not executing echo that does not turn it into a failure.
Using both || and && in the same line of code, is not a good practice, while it might work on some cases/situations, that does not mean it will work on all cases.
see http://mywiki.wooledge.org/BashPitfalls#cmd1_.26.26_cmd2_.7C.7C_cmd3
Always use an if statement if you feel like doing a short circuit.
Command grouping is a work around if you really need to do that, using the { }
echo this || { echo that && echo other; }
&& and || have the same order of precedence.
So echo this || echo that occurs first and then && echo other.
The effect of:
command1 || command2
is as follows:
If command1 returns zero: command2 is not executed, and overall return value is 0.
If command1 returns non-zero: command2 is executed, and overall return value is that of command2.
It is somewhat analogous to short-circuit evaluation of an "or" operator in various programming languages (e.g. a || b in C) except that a "true" value is a zero (i.e. successful) exit status rather than a non-zero value of an expression.
Given that the echo statements return 0, this means that the effect of
echo this || echo that
is to execute echo this but not echo that, and to have an overall exit status of 0.
Given this, and that the || and && are treated with equal precedence (so are evaluated from left to right), the command sequence:
echo this || echo that && echo other
will also cause echo other to be executed (because foo && bar will execute bar if foo returned 0).
So, command1 || command2 && command3 is valid, but if should not be read as meaning:
(a) Run command1. Then if command1 failed then run command2 but if command1 succeeded then run command3.
Instead, it should be read as:
(b) Run command1. If command1 failed then run command2. If either (command1 succeeded) or (command1 failed but command2 succeeded), then run command3.
(where "succeeded" means exited with zero status).
If the intention is as described in (a), then this should instead be implemented using:
if ! command1 ; then command2 ; else command3 ; fi
or
if command1 ; then command3 ; else command2 ; fi
Related
One thing I don't get with this operators is when I use two of them in sequence.
What I mean by that:
% true && echo "problem"
problem
% echo $?
0
So far, so good. true returns "error" (exit status 1) and echo "problem" returns 0, so logical AND operation result must be 0.
% true && echo "problem" || echo "exit"
problem
OK, that's a surprise: since true && echo "problem" results in 0, || should also evaluate echo "exit", since after all right-hand operand of || might be true and so the result of this logical OR might be true.
Now:
% true && echo "problem" && echo "exit"
problem
exit
This is also surprising: after all since true && echo "problem" returns zero, the lazy && operator should not evaluate echo "exit" since the result of logical AND must be zero anyway.
Why is the behavior of last two examples opposite to what I intuitively expect?
P.S. This is opposite of Python behavior:
% python
>>> def pp():
... print "problem"
...
>>> def pe():
... print "exit"
>>> True and pp() and pe()
problem
>>> True and pp() or pe()
problem
exit
You're misunderstanding success/failure statuses and also what && and || mean in this context in bash.
An exit status of success is 0, while failure is non-zero so when you say true returns "error" (exit status 1), no it doesn't. Also, nothing in shell "returns" anything. Scripts and functions produce output and have an exit status - using the word "return" leads to confusion over which of those 2 separate things you mean. For example if we define this function:
foo() {
echo "hello"
return 7
}
and then use it to populate a variable:
var=$(foo)
$ echo "$var"
hello
did foo() "return" hello or did foo() "return" 7? The best answer is no, it didn't "return" either - it output hello and exited with status 7.
Although there's a poorly-named "return" keyword there what that REALLY is producing is an exit status for the function, the same as if you had a shell script that was just:
echo "hello"
exit 7
and you can see that if you test it ($? always holds the exit status of the most recently run command):
foo() {
echo "hello"
return 7
}
$ foo
hello
$ echo "$?"
7
I assume the shell creators chose "return" for the function keyword because "exit" already meant "exit from the running process" but IMHO that made things confusing, though I don't have a better suggestion and even if I did that ship has sailed long ago. If you read return 7 as set the exit status to 7 then return from the function without assuming the function is actually "returning" anything then you'd be right.
Note also that the function is outputting "hello" - that's also not a "return" but if you use it as var=$(foo) then var ends up containing hello so then some people incorrectly refer to that as a "return" too since in other languages like C if you wrote var=foo():
char *foo() {
return "hello"
}
var=foo()
then var would contain the argument that was given to return in the function but that is just not the same semantics as shell where, unlike the similar C code above, var=$(foo) sets var to the output from foo(), not the "return" (actually exit status) from foo().
So - the function above doesn't actually "return" anything, it outputs hello and exits with status 7.
So here's what your command line true && echo "problem" || echo "exit" actually does:
true = output nothing and exit with status 0 (success)
echo "problem" - output problem and exit with status 0 (success)
echo "exit" - output exit and exit with status 0 (success)
Now, what do && and || mean? What they really are is shorthand for if statements:
&& foo = if the previously run command exited success then execute foo
|| foo = if the previously run command exited failure then execute foo
So a command line like:
cmdA && cmdB || cmdC
in terms of success/fail status should be read as:
cmdA
ret=$?
if (( ret == 0 )); then
cmdB
ret=$?
fi
if (( ret != 0 )); then
cmdC
ret=$?
fi
( exit "$ret" )
We need the ret temp variable because the if itself has an exit status that'd overwrite $?. So cmdC will get called if cmdA exits with a failure status, but it'll also get called if cmdA succeeded and then cmdB exited with a failure status. At the end of cmdA && cmdB || cmdC the exit status as stored in $? will simply be the exit status of whichever command ran last, it will not, for example, be the product of boolean arithmetic on all of the exit statuses of all the commands that ran as apparently suggested in the question might be the case.
Note also that what that should NOT be read as is what you may intuitively have expected if you thought of && ... || ... as a ternary expression, which trips many people up, especially since that || is typically an error leg whose incorrect placement may escape your code inspectors/testers notice:
cmdA
if (( $? == 0 )); then
cmdB
else
cmdC
fi
Given the above, here's what your command lines actually mean:
true && echo "problem" || echo "exit"
true
ret=$?
if (( ret == 0 )); then
echo "problem"
ret=$?
fi
if (( ret != 0 )); then
echo "exit"
ret=$?
fi
( exit "$ret" )
true && echo "problem" && echo "exit"
true
ret=$?
if (( ret == 0 )); then
echo "problem"
ret=$?
fi
if (( ret == 0 )); then
echo "exit"
ret=$?
fi
( exit "$ret" )
and if what you WANTED to have happen instead of "2" above was actually:
true
if (( $? == 0 )); then
echo "problem"
else
echo "exit"
fi
then you should write that code or similar instead of using &&s and ||s (e.g. as 1 line you could write if true; then echo "problem"; else echo "exit"; fi) so you don't get unexpected output if you reach the echo "problem" leg and it fails for some reason thereby causing you to afterwards fall into the echo "exit" leg (unlikely with just echo but very possible with other commands).
Assuming thoses functions :
return_0() {
return 0
}
return_1() {
return 1
}
Then the following code :
if return_0; then
echo "we're in" # this will be displayed
fi
if return_1; then
echo "we aren't" # this won't be displayed
fi
if return_0 -a return_1; then
echo "and here we're in again" # will be displayed - Why ?
fi
Why I am getting into the last ifstatement ?
Aren't we supposed to be out of the condition with those 0 and 1 ?
-a is one of the options of the test command (which is also implemented by [ and [[). So you can't just use -a by itself. You probably want to use &&, which is a control operator token for an AND list.
if return_0 && return_1; then ...
You can use -a to tell test to "and" two different test expressions, like
if test -r /file -a -x /file; then
echo 'file is readable and executable'
fi
But this is equivalent to
if [ -r /file -a -x /file ]; then ...
which may be more readable because the brackets make the test part of the expression clearer.
See the Bash Reference Manual for further information on...
&&, see lists
if statements and the various test commands and keywords, see conditional constructs
When you execute
if return_0 -a return_1; then
echo "and here we're in again" # will be displayed - Why ?
fi
You execute the line return_0 -a return_1. This actually means that you pass -a and return_1 as arguments to return_0. If you want to have an and operation, you should make use of the && syntax.
if return_0 && return_1; then
echo "and here we're in again" # will be displayed - Why ?
fi
The useful information to understand this is:
AND and OR lists are sequences of one of more pipelines separated by the && and || control operators, respectively. AND and OR lists are executed with left associativity. An AND list has the form
command1 && command2
command2 is executed if, and only if, command1 returns an exit status of zero.
An OR list has the form
command1 || command2
command2 is executed if and only if command1 returns a non-zero exit status. The return status of AND and OR lists is the exit status of the last command executed in the list.
I am setting up a script that will execute a series of bundle exec rspec commands and I want it to return false if any of them fail. But I still want it to run all the tests. Is there a shorter way to accomplish this without a bunch of if or test statements?
I'm doing it currently like this, but I'll update the answer with nicer syntax if I'm able to write a bash function to handle this:
#!/bin/sh
set +x
RETVAL=0
command1 || RETVAL=1
command2 || RETVAL=1
command3 || RETVAL=1
exit $RETVAL
This is a for loop that will go through all the return codes and if one failed will exit with the first seen failed return code.
i=0
rc=0
command1
rcode[i]=$?
i=i+1
command2
rcode[i]=$? ... n
for i in "${rcode}"
do
if [ $i -ne 0 ]; then
rc=$i
break
fi
done
exit $rc
Track their exit code in a variable, and exit with it. I added the line number to for troubleshooting what broke.
declare -i r_code=0 # return code
command1 || { r_code+=$?; echo "ERROR at $LINENO
}
command2 || { r_code+=$?; echo "ERROR at $LINENO
}
exit $r_code
I'd like to exit a script when an operation could not be executed.
non-existing || exit 1;
works fine and exits, while
ls || exit 1;
does not exit. Now, I'd also like to add an error message before exiting the program.
non-existing || echo "Having trouble" && exit 1;
exits as expected, but
ls || echo "Having trouble" && exit 1;
also exits, although the echo command (and thus the exit) should not be executed.
I first thought that using brackets might help:
ls || (echo "Having trouble" && exit 1;)
Does not exit. But when I invoke a non-existing program, the exit-command only exits from the sub-shell that I opened using the brackets:
non-existing || (echo "Having trouble" && exit 1;)
echo "Still executed."
How can I exit a program with an error message in bash?
Why does ls || echo "Having trouble" && exit 1; exit?
You can use Use { ...; } instead of (...) to avoid the sub-shell and the problem that causes and to get the result that you want.
Note that a space after the opening { and a semicolon/etc. and a space before the closing } are required for this (where they are optional with ()).
As to why ls || echo && exit doesn't do what you expect the answer is because the || and && operators have the same precedence and are left-associative (see the POSIX spec).
So when the shell sees
ls || echo "Having trouble" && exit 1;
you think it is
ls || { echo "Having trouble" && exit 1; }
but the shell parses it as
{ ls || echo "Having trouble"; } && exit 1;
Or, as the spec puts it:
The operators "&&" and "||" shall have equal precedence and shall be evaluated with left associativity. For example, both of the following commands write solely bar to standard output:
false && echo foo || echo bar
true || echo foo && echo bar
According to this accepted answer using the set -e builtin should suffice for a bash script to exit on the first error. Yet, the following script:
#!/usr/bin/env bash
set -e
echo "a"
echo "b"
echo "about to fail" && /bin/false && echo "foo"
echo "c"
echo "d"
prints:
$ ./foo.sh
a
b
about to fail
c
d
removing the echo "foo" does stop the script; but why?
To simplify EtanReisner's detailed answer, set -e only exits on an 'uncaught' error. In your case:
echo "about to fail" && /bin/false && echo "foo"
The failing code, /bin/false, is followed by && which tests its exit code. Since && tests the exit code, the assumption is that the programmer knew what he was doing and anticipated that this command might fail. Ergo, the script does not exit.
By contrast, consider:
echo "about to fail" && /bin/false
The program does not test or branch on the exit code of /bin/false. So, when /bin/false fails, set -e will cause the script to exit.
Alternative that exits when /bin/false fails
Consider:
set -e
echo "about to fail" && /bin/false ; echo "foo"
This version will exit if /bin/false fails. As in the case where && was used, the final statement echo "foo" would therefore only be executed if /bin/false were to succeed.
Because that answer is not sufficiently specific enough.
It should say (bolded text is my addition):
# Any subsequent simple commands which fail will cause the shell script to exit immediately
Since the man page reads thusly:
-e 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.
And SHELL GRAMMAR expands thusly:
SHELL GRAMMAR
Simple Commands
A simple command is a sequence of optional variable assignments fol-
lowed by blank-separated words and redirections, and terminated by a
control operator. The first word specifies the command to be executed,
and is passed as argument zero. The remaining words are passed as
arguments to the invoked command.
The return value of a simple command is its exit status, or 128+n if
the command is terminated by signal n.
I came across set -e for Bash scripts but had problems understanding what happens regarding the evaluation of the command following the last && or || in a && or || list. I know of the following quote from http://man7.org/linux/man-pages/man1/bash.1.html about set -e:
The shell does not exit if the command that fails is (...) part of
any command executed in a && or || list except the command
following the final && or || (...)
To test this, I wrote a small Bash script:
#!/bin/bash
bash -c "set -e ; true ; echo -n A"
bash -c "set -e ; false ; echo -n B"
bash -c "set -e ; true && true ; echo -n C"
bash -c "set -e ; true && false ; echo -n D"
bash -c "set -e ; false && true ; echo -n E"
bash -c "set -e ; false && false ; echo -n F"
bash -c "set -e ; true || true ; echo -n G"
bash -c "set -e ; true || false ; echo -n H"
bash -c "set -e ; false || true ; echo -n I"
bash -c "set -e ; false || false ; echo -n J"
echo ""
It prints:
ACEFGHI
About A:
true does not have a non-zero status. Therefore, the shell doesn't exit.
About B:
false does have a non-zero status and is not part of a && or || list. Therefore, the shell exits.
About C:
This is a && or || list. We will have to look at the command following the last && or ||. The command is true which does not have a non-zero status. So it doesn't matter if the command is evaluated or not - the shell doesn't exit either way.
About D:
This is a && or || list. We will have to look at the command following the last && or ||. This time, the command is false which does have a non-zero status. So we have to check if that false is being evaluated - which is indeed the case since && is following the true. Therefore, the shell exits.
About E:
Same reasoning as with C: true is the command following the last && or ||. Therefore, the shell doesn't exit.
About F:
Similar to D: This is a && or || list. We will have to look at the command following the last && or ||. Again the command is false which does have a non-zero status. But this time it doesn't matter, because the first command is false as well. Since it's a && list, the second false won't be evaluated. Therefore, the shell doesn't exit.
About G:
Same reasoning as with C or E: true is the command following the last && or ||. Therefore, the shell doesn't exit.
About H:
This is a && or || list. We will have to look at the command following the last && or ||. This command is false which does have a non-zero status, but it won't be evaluated since || is preceded by true. Therefore, the shell doesn't exit.
About I:
Same reasoning as with C, E or G: true is the command following the last && or ||. Therefore, the shell doesn't exit.
About J:
This is a && or || list. We will have to look at the command following the last && or ||. This command is false which does have a non-zero status. Since || is preceded by false the second false will be evaluated. Therefore, the shell does exit.
You should be able to apply these test cases to your case: true && false && true. Since the command following the last && or || is true which doesn't have a non-zero status, it doesn't matter what precedes && or ||, the shell won't exit either way.