4

Consider the following:

root@debian-lap:/tmp echo "Step 1 " || echo "Failed to execute step 1" ; echo "Step 2"
Step 1
Step 2
root@debian-lap:/tmp

As you can see, the 1st and 3rd echo command executed normally.

And if I the first command failed I want to stop the script and exit from it:

root@debian-lap:/home/fugitive echo "Step 1 " || echo "Failed to execute step 1" && exit 2 ; echo "Step 2"
Step 1 
exit
fugitive@debian-lap:~$ 

The exit command executes and exits the shell, even thou exit code of the first command is 0. My question is - why?

In translation, doesn't this say:

  • echo "Step 1"
  • if the command failed , echo 'Failed to execute step 1' and exit the script
  • else echo "Step 2"

Looking at this like :

cmd foo1 || cmd foo2 && exit

Shouldn't cmd foo2 and (&&) exit execute only when cmd foo1 failed?

What I am missing?

Edit

I am adding 2nd example, something that I am really trying to do (still dummy test)

root@debian-lap:/tmp/bashtest a="$(ls)" || echo "Failed" ; echo $a
test_file  # < - This is OK
root@debian-lap:


root@debian-lap:/tmp/bashtest a="$(ls)" || echo "Unable to assign the variable" && exit 2; echo $a
exit
fugitive@debian-lap:~$   # <- this is confusing part

root@debian-lap:/tmp/bashtest a="$(ls /tmpppp/notexist)" || echo "Unable to assign the variable" ; echo $a
ls: cannot access /tmpppp/notexist: No such file or directory
Unable to assign the variable            # <- This is also OK
root@debian-lap:
Rui F Ribeiro
  • 56,709
  • 26
  • 150
  • 232
fugitive
  • 1,563

2 Answers2

8

Because the last command executed (the echo) succeeded. If you want to group the commands, it's clearer to use an if statement:

if ! step 1 ; then
   echo >&2 "Failed to execute step 1"
   exit 2
fi

You could also group the error message and exit with { ... } but that's somewhat harder to read. However that does preserve the exact value of the exit status.

step 1 || { echo >&2 "step 1 failed with code: $?"; exit 2; }

Note, I changed the && to a semicolon, since I assume you want to exit even if the error message fails (and output those errors on stderr for good practice).

For the if variant to preserve the exit status, you'd need to add your code to the else part:

if step 1; then
  : OK, do nothing
else
  echo >&2 "step 1 failed with code: $?"
  exit 2
fi

(that also makes it compatible with the Bourne shell that didn't have the ! keyword).


As or why the commands group like the do, the standard says:

An AND-OR list is a sequence of one or more pipelines separated by the operators "&&" and "||".

The operators "&&" and "||" shall have equal precedence and shall be evaluated with left associativity.

Which means that something like somecmd || echo && exit acts as if somecmd and echo were grouped together first, i.e. { somecmd || echo; } && exit and not somecmd || { echo && exit; } .

ilkkachu
  • 138,973
  • But how the 2nd echo succeeded when I've declared || after the first command ? Thats what confuses me :) – fugitive Jun 20 '17 at 12:23
  • @fugitive, edited to elaborate on that – ilkkachu Jun 20 '17 at 14:20
  • 2
    Also, note that A && B || C is not the same as if A; then B; else C; fi when we are think about what happens if A succeeds but B fails. I'd recommend to stick with the if command to avoid confusion and ambiguity. – glenn jackman Jun 20 '17 at 14:24
  • @ilkkachu Thanks man for explanation, now it makes sense! @glennjackman thanks, I know that this is bad example, and if is better but wanted to troubleshoot this. :) – fugitive Jun 20 '17 at 14:49
1

I believe the problem lies in the priority of binary/logical operators. Namely "and" and "or" have the same priority, and as such the following line:
echo "Step 1 " || echo "Failed to execute step 1" && exit 2 ; echo "Step 2" is essentially the same as:
( echo "Step 1 " || echo "Failed to execute step 1" ) && exit 2 ; echo "Step 2".
You should try echo "Step 1 " || ( echo "Failed to execute step 1" && exit 2 ) ; echo "Step 2" instead.