3

Note: If you are looking for a workaround because set -e does not work in a function, please go to “set -e” in a function. This question is about why it does not work as expected.

When the following is run on GNU bash, version 4.1.5(1)-release :

  set -ex

  PS4=' ${FUNCNAME[0]}: $LINENO: '

  function f() {
      set -e
      false
      echo $@
  }

  ! f why does it display ? It should stop because of -e
  f

It displays

 : 11: f why does it display '?' It should stop because of -e
 f: 6: set -e
 f: 7: false
 f: 8: echo why does it display '?' It should stop because of -e
why does it display ? It should stop because of -e
 : 12: f
 f: 6: set -e
 f: 7: false

I expect it to never go past the false, because -e means "exit when a command has a non zero exit status". I am aware that -e has a tricky behavior, as explained in http://mywiki.wooledge.org/BashFAQ/105 but I would like to understand what happens in this specific case. I am using -e and it proved most helpful in many very simple scenarios. This scenario is a little more tricky but if it can be explained I may use -e instead adding || exit 1 after each line.

Community
  • 1
  • 1
Loic Dachary
  • 1,034
  • 1
  • 10
  • 24
  • From the 4.2 bash ref manual: "If the reserved word ‘!’ precedes the pipeline, the exit status is the logical negation of the exit status as described above." So do you want "true" instead of "false"? – grok12 Jul 10 '11 at 16:25
  • No, I don't want true. I want the echo $@ to never be run because of the -e and the preceding false. – Loic Dachary Jul 11 '11 at 09:16
  • Why do you mix comments and output? It just makes it unreadable. – Tomas Pruzina Mar 27 '12 at 09:15

3 Answers3

6

While trying to solve a similar problem, I read the man page for set -e:

-e errexit

Exit immediately if a simple command exits with a non-zero status, unless the command that fails is part of an until or while loop, part of an if statement, part of a && or || list, or if the command's return status is being inverted using !.

Note the last bit about the return status being inverted. I guess the logical negation of function f must have an effect on the behaviour of set -e within that function.

kenorb
  • 155,785
  • 88
  • 678
  • 743
2

Here's from the trap section of man bash (my emphasis):

If a sigspec is ERR, the command arg is executed whenever a simple command has 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, or if the command's return value is being inverted via !. These are the same conditions obeyed by the errexit option.

So ! command effectively disables the ERR trap inside command. I guess the reasoning is that you might not want a command exiting early if you are "kinda explicitly" checking the exit code after running it with !. Looks like bad design, since it can be used to effectively disable safeguards pretty much anywhere.

l0b0
  • 55,365
  • 30
  • 138
  • 223
1

I can't precisely answer the why, but I found this snippet here:

On a slightly related note, by default bash takes the error status of the last item in a pipeline, which may not be what you want. For example, false | true will be considered to have succeeded. If you would like this to fail, then you can use set -o pipefail to make it fail.

And by updating your script as such:

function f() {
    echo $SHELLOPTS
    set -e
    set -o pipefail
    false
    echo "$@"
}

It seems to behave as you expect.

So my best guess for "why" (probably the same as yours by now), is that the ! causes the function to be handled in some sort of "pipe" mode. Again... why ! means 'pipe', I guess I don't really know. Maybe a better bash expert can answer that part for us.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189