"foo && bar || baz" in bash behaving differently from "if foo; then bar; else baz"
Except for the overall exit status, it acts like:
if
! {
[ 1 -eq $1 ] && nocmd "yes"
}
then
echo no
fi
In:
A || B
B
is executed iff A
fails. That's a OR operator.
In your case A
is [ 1 -eq $1 ] && nocmd "yes"
where nocmd
is run iff [
succeeds (a AND operator), in which case the exit status of A
will be that of nocmd
. In other words echo no
will be executed if either [
or nocmd "yes"
fails (bearing in mind that nocmd
is only run if [
succeeds).
Those x && y || z
are dirty hacks. They are best avoided for that very reason. Use a if
/then
/else
construct if you do want a if/then/else logic. Use x && y || z
only if you want z
to be unless both x
and y
succeeded.
Even in:
cmd && echo OK || echo >&2 KO
The echo OK
could fail under some pathological conditions (like stdout going to a file on a full filesystem), and echo >&2 KO
could end up being executed as well.
$ bash -c 'true && echo OK || echo KO >&2' > /dev/full
bash: line 0: echo: write error: No space left on device
KO
Well, what actually happens is this (and this is how &&
and ||
works)
test 1 -eq $1
ret=$?
if [ $ret -eq 0 ]; then
nocmd "yes"
ret=$?
fi
if [ $ret -ne 0 ]; then
echo "no"
fi
In your case, if $1
does not equal 1
, or is not a valid number, then you have a non-zero $?
, and the first if
is skipped, and the second if
prints "no". If $1
equals 1
, then nocmd "yes"
is executed, returning a non-zero $?
, and "no" is also echoed.
What you're missing is that &&
and ||
operate on exit status of commands to the left of them - left associativity. You have here some group of commands || echo "no"
, and no
will be echoed if and only if that group of commands returns non-success exit status.
What is that group of commands ? In first case you have [ 1 -eq "$1" ] && echo "yes"
. If [
portion failed, that'd be counted as fail exit status for [ 1 -eq "$1" ] && echo "yes"
, therefore you'd echo "no"
would run. Also because of left associativity, when "$1" is 1, the [ 1 -eq $1 ]
returns success, then lets nocmd
run which doesn't exist and shell will return error exit status, the whole group will have exit status of fail, hence `"no" is echoed.
By contrast, in your if statement
if [ 1 -eq $1 ]; then
echo "yes"
else
echo "no"
fi
which route to take depends only on exit status of [
portion. In [ 1 -eq "$1" ] && echo "Yes" || echo "no"
echo also plays role as to whether or not echo "no"
runs.
Your second if statement example is also different
if [ 1 -eq $1 ]; then
nocmd "yes"
if [ $? -ne 0 ]; then
echo "no"
fi
else
echo "no"
fi
The echo "no"
after else
doesn't depend on what happens to nocmd
as in the chaining of logical operators. Sure you still do the echo "no"
part after doing nocmd
, but here its exit status isn't grouped together with the whole if
portion to which it belongs.