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:
3
  • Related: Precedence of logical operators versus semicolon Commented Jun 20, 2017 at 13:12
  • You should really add a \$ to the end of your $PS1 value.
    – Wildcard
    Commented Jun 20, 2017 at 22:03
  • @Wildcard I've removed due snippets, ofc it is included. As commands I've shown are executed in root, the # makes rest of the line commented out.
    – fugitive
    Commented Jun 21, 2017 at 8:20

2 Answers 2

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; } .

4
  • But how the 2nd echo succeeded when I've declared || after the first command ? Thats what confuses me :)
    – fugitive
    Commented Jun 20, 2017 at 12:23
  • @fugitive, edited to elaborate on that
    – ilkkachu
    Commented Jun 20, 2017 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. Commented Jun 20, 2017 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
    Commented Jun 20, 2017 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.

You must log in to answer this question.

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