2

I have default bash v4.4.12 set up on Debian:

$ bash --version
GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)

I prefer to use these options in my scripts:

set -o pipefail
set -o errexit
set -o nounset

It make stop a script on non-zero result in pipe-commands (pipefail), execute exit (errexit), and validate unset variables (nounset).

I have test script:

set -o pipefail
set -o errexit
set -o nounset
set -o xtrace

return_1() { return 22; }

test_1 () {
  return_1;
  echo 'after';
}

success_1() {
  echo success
}

if [ "${1:-}" == 1 ]; then
  # make "return 1" in the root level
  return_1
fi

if [ "${1:-}" == 2 ]; then
  # run test_1, we will get "return 1" within a function
  test_1
fi

if [ "${1:-}" == 3 ]; then
  # run test_1 and success_1 in chain
  # success_1 MUST NOT be ran because test_1 makes non-zero status
  # in fact, success_1 will be ran =(
  test_1 && success_1
fi

Testing.

$ bash /test.sh 1; echo "status: ${?}"
+ '[' 1 == 1 ']'
+ return_1
+ return 22
status: 22

Works as expected.

$ bash /test.sh 2; echo "status: ${?}"
+ '[' 2 == 1 ']'
+ '[' 2 == 2 ']'
+ test_1
+ return_1
+ return 22
status: 22

Everything is right. The line "echo 'after';" haven't called.

$ bash /test.sh 3; echo "status: ${?}"
+ '[' 3 == 1 ']'
+ '[' 3 == 2 ']'
+ '[' 3 == 3 ']'
+ test_1
+ return_1
+ return 22
+ echo after
after
+ success_1
+ echo success
success
status: 0

Completely NOT right. :( 1. The line "echo 'after';" have called. 2. The function "success_1" have called as well.

Really, what's going on in this case?

UPD Manual refferences.

http://man7.org/linux/man-pages/man1/bash.1.html

Kirby
  • 2,847
  • 2
  • 32
  • 42

1 Answers1

6

You've fallen into the classic trap of using set -e. Do carefully read through Why doesn't set -e (or set -o errexit, or trap ERR) do what I expected?

From the GNU bash documentation for set -e

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 in an if statement, part of any command executed in a && or || list except the command following the final && or ||

What do you think happens for this code?

#!/usr/bin/env bash
set -e
test -d nosuchdir && echo no dir
echo survived

Run it for yourself and observe why set -e has no effect on commands run with && or ||. The same has happened for the 3rd case you've reported. Even though the function test_1 is returning a non-zero exit code, using another function as part of && has forced the shell to ignore the errorexit option set.

Better to avoid set -e and use your own error check added. In this case use the function's return code in the if condition and negate the result

if ! test_1; then
    success_1
fi

Also read through Raise error in a Bash script which has some well written answers on how to error handling the best way in shell.

Inian
  • 80,270
  • 14
  • 142
  • 161
  • is there some best practice to handle errors at your hand? `set -o errexit` is the best for me so far. – Kirby Jul 26 '18 at 10:45