Consider two conditional expressions expr1
and expr2
, for example $i -eq $j
and $k -eq $l
. We can write this in bash
a number of ways. Here are two possibilities
[[ expr1 || expr2 ]]
[[ expr1 ]] || [[ expr2 ]]
I'm fairly sure I've seen recommendations here that the second should be preferred, but I can't find evidence to support this.
Here is a sample script that seems to demonstrate there is no difference:
for i in 0 1
do
for j in 0 1
do
for k in 0 1
do
for l in 0 1
do
if [[ $i -eq $j || $k -eq $l ]]; then printf "1-yes\t"; else printf "1-no\t"; fi
if [[ $i -eq $j ]] || [[ $k -eq $l ]]; then printf "2-yes\n"; else printf "2-no\n"; fi
done
done
done
done
and output showing that both condition constructs produce the same result:
1-yes 2-yes
1-yes 2-yes
1-yes 2-yes
1-yes 2-yes
1-yes 2-yes
1-no 2-no
1-no 2-no
1-yes 2-yes
1-yes 2-yes
1-no 2-no
1-no 2-no
1-yes 2-yes
1-yes 2-yes
1-yes 2-yes
1-yes 2-yes
1-yes 2-yes
Is there any benefit to using one construct over the other?
For bonus points, same question but generalising to multiple conditions using ||
and &&
. For example, [[ expr1 && expr2 || expr3 ]]
.
Best Answer
I think the recommendation you saw was for POSIX sh and/or the
test
command which doubles as the[
command, rather than the[[
construct which appeared in ksh (thanks Stéphane Chazelas for the tip) and is also used for example in bash, zsh and some other shells.In most languages, like C, when a clause is already known to be true or false, there's no need to evaluate the remainder parts depending on the operation: if true not after a logical or following it, if false not after a logical and, etc. This of course allows for example to stop when a pointer is NULL and not try to dereference it in the next clause.
But sh's
[ expr1 -o expr2 ]
construct (including bash's implementation) doesn't do this: it always evaluates both sides, when one would want only expr1 to be evaluated. This might have been done for compatibility with thetest
command implementation. On the other hand, sh's||
and&&
do follow the usual principle: not evaluated if it won't change the result.So the difference to note would be:
which yields:
Above, each
[
could have been replaced with/usr/bin/[
which is an alias to thetest
command, used before[
was made built-in to shells.While the two next constructs:
or
Will only yield
true
and leaveeffect
empty:||
behaves correctly, and[[
corrected this issue too.UPDATE:
As @StéphaneChazelas commented, I missed several differences related to the initial question. I'll just put here the most important one (at least to me): priority of operators.
While the shell will not considere precedence:
yields (because there is no precedence and thus first
true || true
is evaluated, and then&& false
):inside
[[ ]]
the&&
operator has precedence over||
:yields (because
1 -eq 1 && 1 -eq 0
is grouped and is thus the 2nd member of||
):at least for ksh, bash, zsh.
So
[[ ]]
has improved behaviour over both[ ]
and direct shell logical operators.