4

According to man bash,

set -e

Exit immediately if (snip). A trap on ERR, if set, is executed before the shell exits.

However, the script below doesn't invoke the ERR trap.

trap 'echo ERR; sleep 1' ERR
trap 'echo EXIT; sleep 1' EXIT

set -e

array=(a b c)
echo $(( ${#array[@] - 1)) #note the closing bracket } is forgotten

echo "after"

The expected result is

$ bash script.sh 
4.sh: line 7:  ${#array[@] - 1: bad substitution
ERR
EXIT
# and then shell exits

The actual result is

$ bash script.sh 
4.sh: line 7:  ${#array[@] - 1: bad substitution
EXIT
# and then shell exits

If I delete the line set -e, then

$ bash script2.sh
4.sh: line 7:  ${#array[@] - 1: bad substitution
after #the last echo command is executed
EXIT

This means set -e catches the syntax error. Why isn't the ERR trap invoked although the shell does exit?


Environment:

I tested the script on two machines.

$ bash --version
GNU bash, version 4.4.12(1)-release (arm-unknown-linux-gnueabihf)
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ bash --version
GNU bash, version 5.0.11(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Supplement:

According to the posted answers, it is natural the trap isn't executed and this is because echo $(( ${#array[@] - 1)) doesn't complete its execution to return exit status. Is my understanding right?

However, man bash explains set -e as

Exit immediately if a pipeline (which may consist of a single simple command), a list, or a compound command (see SHELL GRAMMAR above), exits with a non-zero status.

I think this also requires the command to complete. If echo $(( ${#array[@] - 1)) doesn't complete its execution, I believe set -e should not work here and echo "after" should be executed.


Supplement2:

According to oguz ismail's comment, this is a documentation issue. POSIX says set -e shall exit the shell when

  • non-zero exit status is returned (as you can see in man bash)

  • or shell error occurs

and shell error includes syntax error. But man bash is missing the explanation for the second case, right? If so, everything is consistent other than the fact that man bash doesn't explain the implementation accurately and the statement "These are the same conditions obeyed by the errexit (-e) option." found in the trap's explanation in man bash.

Community
  • 1
  • 1
ynn
  • 3,386
  • 2
  • 19
  • 42
  • I believe the question is on topic here, however you might want to also post on the [Unix&Linux SE](http://unix.stackexchange.com/) where you might have more chance to get an answer quickly. – Aaron Oct 10 '19 at 08:28
  • @Aaron Thank you for your advice. I'll consider it if there is no good answer posted. – ynn Oct 10 '19 at 08:40
  • You seem to be missing the point that `ERR` tests return codes from *completed* commands, not from commands that have not been executed (how can it?). What you are observing is correct and expected behaviour. – cdarke Oct 11 '19 at 08:15
  • @cdarke Ok, I understand it. But then why does `set -e` catch the syntax error? `man bash` doesn't say it catches syntax errors. (POSIX says so (see Suppliment2 in OP), but `bash` isn't POSIX compliant.) – ynn Oct 11 '19 at 10:27
  • Looking at the source code, the `set -e` (`errexit`) and `traps ERR` use different code and mechanisms to test and action errors. The `set -e` exits immediately, the `ERR` trap only tests when a command is run, that's why the `set -e` catches the syntax error and the `ERR` trap does not. `man bash` implies that the two mechanisms act in the same way, but clearly they don't and they never have. – cdarke Oct 11 '19 at 13:28
  • Agree with @cdarke completely. If you are not convinced still on be answers, try upping it with a bounty and see if you can attract more comprehensive answers – Inian Oct 11 '19 at 16:25
  • @cdarke "man bash implies that the two mechanisms act in the same way, but clearly they don't and they never have." How clearly? From the source code? I don't think that is clear. Rather, the behavior contradicts with `man bash`. Normal users expect any behaviors not from the source code but from `man bash`, I believe. In other words, `man bash` is all from the perspective of users (non-bash-developer). (I admit the fact that the two commands act differently, but my original question is "why so?" as explicitly written in OP. Because of `man bash`'s bug? Because I miss something in `man bash`?) – ynn Oct 12 '19 at 03:18
  • If cdarke says "*man bash implies that the two mechanisms act in the same way*" and Inian says "*Agree with cdarke completely.*", I don't understand why they cited `man bash` in their answers. They think `man bash` is incomplete, but cite it to answer my question? It seems their answers should be something like "It is a bug of `man bash`. Instead see the source code." to be logically consistent. – ynn Oct 12 '19 at 03:24

2 Answers2

4

This is because of a weird way how set -e is implemented in bash over the years. It comes around with lot of specific cases under which is expected to work.

From man bash:

  • trap ... ERR
    • If a sigspec is ERR, the command arg is executed whenever a pipeline (which may consist of a single simple command), a list, or a compound command returns a non-zero exit status, subject to the following conditions:
      • The ERR trap is not executed if the failed command is part of the command list immediately following a while or until keyword...
      • ...part of the test in an if statement...
      • ...part of a command executed in a && or || list except the command following the final && or ||...
      • ...any command in a pipeline but the last...
      • ...or if the command's return value is being inverted using !.
    • These are the same conditions obeyed by the errexit -e option.

What you have in this example code is a shell expansion error for which the above mentioned clauses never really apply. In all the above clauses an incorrect command runs/completes and sets an return-code back to the shell for your ERR trap to fire.

But when the line echo $(( ${#array[@] - 1)) is encountered, soon after the arithmetic expansion fails, there is no command that is actually run that would fire an ERR trap, because to fire a trap the command needs to be complete. From the man bash page

  • If Bash is waiting for a command to complete and receives a signal for which a trap has been set, the trap will not be executed until the command completes.
Inian
  • 80,270
  • 14
  • 142
  • 161
1

The ERR trap has never trapped syntax errors, and is not designed for that. From man bash:

  If  a  sigspec  is  ERR,  the command arg is executed whenever a
  pipeline (which may consist of a single simple command), a list,
  or a compound command returns a non-zero exit status ...

in this case the command is never executed, so does not return a non-zero status, the script fails before that.

cdarke
  • 42,728
  • 8
  • 80
  • 84
  • `man bash` says `If a sigspec is ERR, the command ard is executed whenever ... These are the same conditions obeyed by the errexit (-e) option.` In my case, `set -e` catches the syntax error, so if `trap ERR` works on the same condition as `set -e`, the `ERR` trap should be executed, I believe. – ynn Oct 10 '19 at 08:37
  • @ynn But it doesn't work on the same condition as `set -e`, that's the point! The `ERR` trap invoked *after* the command is executed, in this case the command is never executed. – cdarke Oct 10 '19 at 08:41
  • But `man bash` also says in the section of `set -e` command that `Exit immediately if a pipeline (which may consist of a single simple command), a list, or a compound command (see SHELL GRAMMAR above), exits with a non-zero status.` So if your interpretation is right, the shell should never exit (`echo "after"` should be executed). But, in my case, the shell exits for some reason. – ynn Oct 10 '19 at 08:48
  • 1
    @ynn: You are missing the excerpt for `trap` in the man page. The trap won't be fired until a command completes, but the line with the syntax error never gets to complete – Inian Oct 10 '19 at 08:50
  • @ynn the standard says *-e When this option is on, when any command fails (for any of the reasons listed in Section 2.8.1, Consequences of Shell Errors or by returning an exit status greater than zero), the shell immediately shall exit*, so it's just a documentation issue – oguz ismail Oct 10 '19 at 08:52
  • Please see my suppliment in OP. – ynn Oct 10 '19 at 09:03