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