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:
2 Answers 2
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; }
.
-
But how the 2nd echo succeeded when I've declared
||
after the first command ? Thats what confuses me :)fugitive– fugitive2017年06月20日 12:23:59 +00:00Commented Jun 20, 2017 at 12:23 -
@fugitive, edited to elaborate on thatilkkachu– ilkkachu2017年06月20日 14:20:47 +00:00Commented Jun 20, 2017 at 14:20
-
2Also, note that
A && B || C
is not the same asif 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 theif
command to avoid confusion and ambiguity.glenn jackman– glenn jackman2017年06月20日 14:24:26 +00:00Commented 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– fugitive2017年06月20日 14:49:54 +00:00Commented Jun 20, 2017 at 14:49
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.
\$
to the end of your$PS1
value.#
makes rest of the line commented out.