Are if else statement equivalent to logical and && or || and where should I prefer one over the other?
Most people find it easier to comprehend the if
... then
... else
... fi
form.
For the a && b || c
, you have to be sure that b
returns true. This is a cause of subtle bugs and is a good reason to avoid this style. If b doesn't return true these are not the same.
$ if true; then false ; else echo boom ; fi
$ true && false || echo boom
boom
For very short tests and actions which don't have an else clause, the shortened length is attractive, e.g.
die(){ printf "%s: %s\n" "$0" "$*" >&2 ; exit 1; }
[ "$#" -eq 2] || die "Needs 2 arguments, input and output"
if [ "$#" -ne 2 ] ; then
die "Needs 2 arguments, input and output"
fi
&&
and ||
are short circuiting operators
, as soon as the result is known further unneeded tests are skipped. a && b || c
is grouped as (a && b) || c
. First a
is run. If it fails
which is defined as not returning an exit status of 0, then the group (a && b)
is known to fail
and b
does not need to be run. The ||
does not know the result of the expression so needs to execute c
. If a
succeeds (returns zero) then the &&
operator doesn't yet know the result of a && b
so has to run b
to find out. If b
succeeds then a && b
succeeds and the ||
knows the overall result is success, so doesn't need to run c
. If b
fails then ||
still does not know the value of the expression, so does need to run c
.
No, constructions if A; then B; else C; fi
and A && B || C
are not equivalent.
With if A; then B; else C; fi
, command A
is always evaluated and executed (at least an attempt to execute it is made) and then either command B
or command C
are evaluated and executed.
With A && B || C
, it's the same for commands A
and B
but different for C
: command C
is evaluated and executed if either A
fails or B
fails.
In your example, suppose you chmod u-r ./myfile
, then, despite [ -f ./myfile ]
succeeds, you will cat /home/user/myfile
My advice: use A && B
or A || B
all you want, this remains easy to read and understand and there is no trap. But if you mean if...then...else... then use if A; then B; else C; fi
.
Operator && executes the next command the if previous command had a successful execution, (returned exit code ($?) 0 = logical true).
In form A && B || C
, command (or condition) A is evaluated and if A returns true (success, exit code 0) then command B is executed.
If A fails (thus will return false - exit code other than 0) and/or B fails (returning false) then command C will be executed.
Also &&
operator is used as an AND in condition checks and operator ||
works like OR in condition checks.
Depending on what you want to do with your script, form A && B || C
can be used for condition checks like your example or can be used to chain commands and ensure a series of commands to be executed if previous commands had a successful exit code 0.
This is why it is common to see commands like:
do_something && do_something_else_that_depended_on_something
.
Examples:
apt-get update && apt-get upgrade
If update fails then upgrade is not executed, (makes sense in the real world...).
mkdir test && echo "Something" > test/file
The part echo "Something"
will be executed only if mkdir test
was successful and operation returned exit code 0.
./configure --prefix=/usr && make && sudo make install
Usually found on compiling jobs to chain necessary dependent commands together.
If you try to implement above "chains" with if-then-else you will need much more commands and checks (and thus more code to write - more things to go wrong) for a simple task.
Also, keep in mind that chained commands with && and || are read by shell left to right. You might need to group commands and condition checks with brackets to depend the next step on the successful output of some previous commands . For example see this:
root@debian:$ true || true && false;echo $?
1
#read from left to right
#true OR true=true AND false = false = exit code 1=not success
root@debian:$ true || (true && false);echo $?
0
# true OR (true AND false)=true OR false = true = exit code 0 = success
Or a real life example:
root@debian:$ a=1;b=1;c=1;[[ $a -eq 1 ]] || [[ $b -eq 1 ]] && [[ $c -eq 2 ]];echo $?
1
#condition $a = true OR condition b = true AND condition $c = false
#=> yields false as read from left to right, thus exit code=1 = not ok
root@debian:$ a=1;b=1;c=1;[[ $a -eq 1 ]] || [[ $b -eq 1 && $c -eq 2 ]];echo $?
0
#vars b and c are checked in a group which returns false,
#condition check of var a returns true, thus true OR false yields true = exit code 0
Keep in mind that some commands return different exit codes depending on the process executed, or return different codes depending on their actions, (for example command GNU diff
, returns 1 if two files differ, and 0 if they don't). Such commands need to be treated with care in && and ||.
Also just to have all the puzzle together, mind the concatenation of commands using ;
operator . With a format A;B;C
all commands will be executed in series no matter what was the exit code of command A
and B
.