0

Let's compare these 2 scripts.

Script 1:

#!/bin/sh

set -e

foo() {
  return 1
}

bar() {
  foo
  echo "this shouldn't be executed"
}

bar

Script 2:

#!/bin/sh

set -e

foo() {
  return 1
}

bar() {
  foo
  echo "this shouldn't be executed"
}

if bar; then
  echo "yes"
else
  echo "no"
fi

Since there's set -e, I expect that if a function returns non-zero value, any function that calls it would also stop on that line and return the same value.

In other words, in the first script, foo return 1, therefore bar will also return 1 and the line echo "this shouldn't be executed" will not execute. In the end, the script will exit with code 1.

However, in the second script, suddenly, if I call bar inside if statement, it will not stop on the line, where it calls foo. It'd continue and echo "this shouldn't be executed".

I don't understand. What's so special about if statements? It seams that inside if statement condition, set -e doesn't have any effect.

Btw, similar unexpected behavior happens if I simply call bar || echo "this should execute". The echo won't happen and instead the line inside bar will execute echo "this shouldn't be executed".

Michal Artazov
  • 4,368
  • 8
  • 25
  • 38
  • It's so hard to find a duplicate because it's hard to find `set -e` and `if`. – KamilCuk Mar 19 '20 at 13:58
  • Don't use `set -e`. A whole set of problems just goes away by following that guidance. – William Pursell Mar 19 '20 at 14:11
  • `set -e` seems like a nice idea. It is often even heavily advised that people use it. But it's not, and you shouldn't. Any corporate standard that suggests using it should be heavily questioned. – William Pursell Mar 19 '20 at 14:13
  • See [BashFAQ #105: Why doesn't `set -e` (or `set -o errexit`, or `trap ERR`) do what I expected?](http://mywiki.wooledge.org/BashFAQ/105) – Gordon Davisson Mar 19 '20 at 21:48
  • Possible duplicate: [How do I get the effect and usefulness of “set -e” inside a shell function?](https://stackoverflow.com/questions/4072984/how-do-i-get-the-effect-and-usefulness-of-set-e-inside-a-shell-function) – Gordon Davisson Mar 19 '20 at 21:54

3 Answers3

2

The exceptions to the -e option are explicitly documented (formatted for emphasis)

-e
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.

The shell does not exit if the command that fails is part of the command list immediately following a while or until keyword, part of the test following the if or elif reserved words, part of any 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 with !.

If a compound command other than a subshell returns a non-zero status because a command failed while -e was being ignored, the shell does not exit. A trap on ERR, if set, is executed before the shell exits. This option applies to the shell environment and each subshell environment separately (see COMMAND EXECUTION ENVIRONMENT above), and may cause subshells to exit before executing all the commands in the subshell.

This extends into function calls; if bar failing as part of the if condition should not cause the shell should exit, neither should a command the fails during the execution of bar.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • but what if I need to call a function and have it fail the standard way **and** I need to wrap it in some form of condition because I need to do some cleanup in case it fails? – Michal Artazov Mar 19 '20 at 13:55
  • If `set -e` would work inside `if` statement, the shell would _exit_ when `foo` returns, it would not execute your if statement. – KamilCuk Mar 19 '20 at 14:00
  • 3
    Then you ditch `set -e` and do your own error handling, which is what I would recommend you *always* do. – chepner Mar 19 '20 at 14:00
  • `trap` is exactly what I was looking for. I'll consider not using `set -e` in the future and instead do my own error handling, but unfortunatelly right now I have a long script that heavily relies on `set -e` and it would take a long time to refactor it which is not something I can afford right now – Michal Artazov Mar 19 '20 at 14:41
1

Parts extracted from bash manual the set builtin:

-e

[...] The shell does not exit if the command that fails is [...] part of the test in an if statement [...]

[...]

If a compound command or shell function executes in a context where -e is being ignored, none of the commands executed within the compound command or function body will be affected by the -e setting, even if -e is set and a command returns a failure status. [...]

What's so special about if statements?

set -e is explicitly ignored inside if commands.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
0

This will no doubt get me criticised for breaking taboos, but personally I prefer to use test constructs than the builtin if or loops for branching. This can sidestep such forms of unpredictability as what you are experiencing.

if bar; then
   echo "yes"
   else
   echo "no"
fi

The above is the mainstream way of doing it. Mine is the latter, below:-

[[ bar ]] && echo "yes"
echo "no"

This second form is closer to what you will encounter in Assembly with its' conditioned jumps, and as a result, in my opinion is more reflective of physical electronics. The test executes first, and although there is no then statement, the second command only executes if the test fails.

Some will criticise this as dangerous; because you need to be more careful about how you order what you are writing, rather than just dumping everything in a series of obvious nests. I can stack as many different conditions as I want this way, but I have to plan the sequence in which I write it myself.

petrus4
  • 616
  • 4
  • 7