6

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?

8
  • @HaukeLaging - No, I meant [ -e doesntexist ] && $((i=1)) actually, but I'd already edited that, hadn't I? I'm specifically talking about the parse order - sometimes commands are evaluated more times than they are executed, which is why : $((i=1)) ; echo $i will get you 1.
    – mikeserv
    Jun 1, 2014 at 21:28
  • I remember a question (or comment discussion) about this equality. I said there that they were not equal in certain circumstances. That referred to lists with more than one && or ||. The difference is that if the then block is executed then the else block is never executed. But with && / || it always depends on the exit code of the currently executed block. Jun 1, 2014 at 21:32
  • I am not sure whether I understand what you mean. : $((i=1)) ; echo $i is a strange example as there is no control flow at all. : is executed (it's a noop, not a comment!), and $((i=1)) is not a command at all but an expansion. Jun 1, 2014 at 21:35
  • [ -e doesntexist ] && $((i=1)) ; echo $i doesn't output 1 – except for if i was 1 before already... Jun 1, 2014 at 21:37
  • 1
    if true; then { echo true; test -e doesntexist; }; else echo false; fi vs. true && { echo true; test -e doesntexist; } || echo false vs. true && { echo true; test -e /; } || echo false Jun 1, 2014 at 21:57

2 Answers 2

4

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).

1
  • Thanks, Gilles. Dang. I knew there was a reason I didn't like accepting them too soon. But you can do cmd && { cmd || cmd ; } right? I mean, that approximates if cmd then { if ! cmd... ; } at least, I think.
    – mikeserv
    Jun 2, 2014 at 0:32
1

if testlist; then else ...fi and testlist && thenlist || elselist are not equivalent in general. The difference is that in the if construct exactly one of thenlist and elselist is executed. In the other case it depends on the exit code of thenlist (and possible eliflist blocks).

Thus you have to add (e.g.) true at the end of a selected result list so that the list has the same exit code like the test list that caused its execution.

if true; then
  echo true
  test -e doesntexist
else
  echo false
fi

can be rewritten with && and || by making sure the exit code of the code blocks. Instead of

true && { echo true; test -e doesntexist; } || echo false

you need

true && { echo true; test -e doesntexist; true; } || echo false

elif

if test -e exists; then
  echo true
elif test -e doesntexist
  echo elif
else
  echo false
fi

would be

test -e exists && { echo true; true; } ||
  { test -e doesntexist && { echo elif; true; } || echo false; }
5
  • Thanks, Hauke. The elif isn't exactly elif, though, right? It's if...fi ; if [ $? = 0 ] .... Same goes for else... approximation as well, I guess, which is why I deleted my comment before - not only is there no elif there's not even any else..., really, only if.../if !..., I guess. It was your own comments that led me to understand that a little better, I think. So I upvoted your answer, but if you could talk about that angle a little I'd accept it. Unless I'm wrong again... Oh, and just echo true - echo always returns 0.
    – mikeserv
    Jun 1, 2014 at 23:12
  • @mikeserv I guess it's an exact elif. &&/|| is always if [ $? -eq 0 ] / if [ $? -ne 0 ] but that doesn't mean that the whole construct is not equivalent to if elif else fi Jun 1, 2014 at 23:32
  • Agreed that the evaluation is equivalent, but the parsing is not, which is what I think I got wrong. Honestly, as I said before, I so seldom use if...fi that I didn't really know, and I thank you for teaching me, but the question is how do they differ? because someone said I was wrong when I said they were equivalent for basically the exact same reasons. So, I guess what I'm asking you to do is to specifically address the question if you would.
    – mikeserv
    Jun 1, 2014 at 23:38
  • @mikeserv I have added the explanation from my question comment to the answer. Jun 1, 2014 at 23:56
  • @mikeserv Whatever. As I recently learnt the hard way you can change the accepted answer later. That's probably easier as you are notified about new answers but AFAIR not reminded of accepting one. I would be a bit surprised to see other answers here anyway... Jun 2, 2014 at 0:09

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .