Shell – How exactly does `if $cmd ; then $cmd ; fi` differ from `$cmd && $cmd`

posixshellshell-script

In an answer to another very good question I made the following assertion:

According to my reading of the POSIX specs, the use of one or the other makes no difference from a parsing standpoint.

POSIX specifies that &&|| lists are compound commands which means that the entire list must be read in and parsed before execution of the constituent simple commands. POSIX also specifies a command shall not be expanded if it follows an ||OR and another command with a 0-exit status. When you consider that a command following an &&|| reserved word can easily be another grouped { command || ( command ; list) ; } makes each branch of the &&|| or list its own compound command.

But POSIX also specifies that each branch of the if...;then...;else...fi construct be its own compound command in this way:

if compound-list
then
    compound-list
[elif compound-list
then
    compound-list] ...
[else
    compound-list]
fi

The if compound-list shall be executed; if its exit status is zero, the then compound-list shall be executed and the command shall complete…

It is this specification that the string following a then... or else... reserved words are compound commands in their own right and not merely simple commands that means the shell's parser must denote them with a command and a delimiter in order to operate correctly.

So basically, then ' ' doesn't work for the same reason:

function()
sh: line 2: syntax error: unexpected end of file 

…doesn't – it doesn't make any sense.

I know that…

[ -e doesntexist ] && $((i=1)) ; echo $i

…will short-circuit any side-effects and result in only a \newline as a result of the spec I noted in my answer. The result of…

if [ -e doesntexist ] 
    then $((i=1))
fi
echo $i

…is identical.

But I honestly use if...fi so seldom that I'm unsure if I've misread something and so when I received a comment indicating my interpretation was incorrect and that if I had further questions on the matter I need only ask I deleted the answer in lieu of this question: how have I got it wrong – how do they differ?

Best Answer

Only simple cases can be expressed with && and ||. The if construct is more general. if CONDITION; then FOO; fi is equivalent to CONDITION && FOO (assuming proper usage of braces to delimit a block if necessary), but as soon as there's an else (or elif), this is no longer possible in general.

if CONDITION; then FOO; else BAR; fi

is not equivalent to

CONDITION && FOO || BAR

If CONDITION is true, both constructs execute FOO. If FOO is false, then the if construct skips BAR, whereas the && … || construct executes BAR.

And no, you can't work around this with CONDITION && { FOO; true; } || BAR. This makes the compound command return true if CONDITION is true and FOO is false, whereas if CONDITION; then FOO; else BAR; fi returns false in that case.

That's for the semantic difference. In addition, there's readability: nested uses of && and || very quickly become hard to decipher. I don't recommend using both in the same command, in fact — especially given that the two operators have equal precedence in the shell, whereas they have different precedences in C and most other C-inspired languages (including Perl and Ruby).