bash chained logical operator execution order, lazy evaluation
&&
and ||
have equal precedence, so:
When a command passes, it will look for the next &&
and execute it, even if it is not the directly adjacent operator. You should never use more than one of these operators in a single command list. If more than one is needed you use an if/then construct.
$ true && true || echo yes && echo no
no
This is very much different than:
if true; then
true
else
echo yes && echo no
fi
$ if true; then true; else echo yes && echo no; fi
$
Or:
$ true && false || echo yes && echo no
yes
no
$ if true; then false; else echo yes && echo no; fi
$
I would write your construct as:
if [ -e filename ]; then
echo filename
elif [ -e ../filename ]; then
echo ../filename
else
echo 'ERROR: failed to find "filename"' >&2
exit -1
fi
I think you are hoping that bash does this:
(A && B) || (C && D) || E
but it actually does this
(A && B || C) && D || E
Where D executes if either B or C succeeds.
Add more grouping:
[ -e filename ] && echo filename || {
[ -e ../filename ] && echo ../filename || {
echo 'ERROR: failed to find "filename"' 1>&2
exit -1
}
}
or
{ [ -e filename ] && echo filename ; } ||
{ [ -e ../filename ] && echo ../filename; } ||
{ echo 'ERROR: failed to find "filename"' 1>&2 ; exit -1; }
or use the very clear and readable if-elif-else
style demonstrated by @Jesse_b.
Step-by-step, this is happening:
filename is in current directory:
[ -e filename ] \ # test succeeds, status is now 0 && # status is zero, will execute this branch echo filename \ # echo succeeds, status is now 0 || # status is zero, do not execute [ -e ../filename ] \ # not executed && # status is zero, will execute this branch echo ../filename \ # echo succeeds, status is now 0 || # status is zero, do not execute { echo; exit; } # not executed
filename is in parent directory:
[ -e filename ] \ # test fails, status is now 1 && # status is non-zero, do not execute this branch echo filename \ # not executed || # status is non-zero, will execute this branch [ -e ../filename ] \ # test succeeds, status is now 0 && # status is zero, will execute this branch echo ../filename \ # echo succeeds, status is now 0 || # status is zero, do not execute { echo; exit; } # not executed