1

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

5

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

4 Comments

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?
If set -e would work inside if statement, the shell would exit when foo returns, it would not execute your if statement.
Then you ditch set -e and do your own error handling, which is what I would recommend you always do.
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
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.

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.