0

The following code:

#!/usr/bin/env bash

set -Eeuo pipefail
shopt -s huponexit
shopt -s inherit_errexit

function child_function {
    return 1
}
function parent_function {
    child_function
    echo "parent noticed child exit code as $?"
}
function grandparent_function {
    local ec
    ec=0 && parent_function || ec="$?"
    echo "grandparent noticed parent exit code as $ec"
}

grandparent_function

Will surprisingly output:

parent noticed child exit code as 1
grandparent noticed parent exit code as 0

Changing the code to:

#!/usr/bin/env bash

set -Eeuo pipefail
shopt -s huponexit
shopt -s inherit_errexit

function child_function {
    return 1
}
function parent_function {
    child_function || return "$?"
    echo "parent noticed child exit code as $?"
}
function grandparent_function {
    local ec
    ec=0 && parent_function || ec="$?"
    echo "grandparent noticed parent exit code as $ec"
}

grandparent_function

Returns the expected result of:

grandparent noticed parent exit code as 1

Is there an additional setting that I need to set to fix this? Or is this a bug in bash?


The reason I believe it is a bug, is that changing the code to not use the || ec="$?" will respect the -e/errexit option (Exit immediately if a command exits with a non-zero status) and have each failed function exit immediately:

#!/usr/bin/env bash

set -Eeuo pipefail
shopt -s huponexit
shopt -s inherit_errexit

function child_function {
    return 1
}
function parent_function {
    child_function
    echo "parent noticed child exit code as $?"
}
function grandparent_function {
    parent_function
    echo "grandparent noticed parent exit code as $?"
}

grandparent_function

Outputs nothing and returns exit code 1

balupton
  • 47,113
  • 32
  • 131
  • 182
  • What version of bash are you running? I'm getting the correct output for both the 1st and 2nd script. The third script exits because of `set -e`. – choroba Aug 08 '23 at 15:48
  • What is the difference between 1st and 2nd script? – KamilCuk Aug 08 '23 at 15:51
  • Sorry, my bad. Made a copy paste mistake... the first script was the same as the second script. I've fixed the first script and now it produces the problem. – balupton Aug 08 '23 at 16:05
  • 2
    It's not a bug. It's a feature. Calling `parent_function` in a conditional context disables `set -e` for it, and everything that it calls. There is no way to get around that. In general, you cannot depend on `set -e` to catch errors. See [BashFAQ/105 (Why doesn't set -e (or set -o errexit, or trap ERR) do what I expected?)](https://mywiki.wooledge.org/BashFAQ/105) and [Bash Pitfalls #60 (set -euo pipefail)](https://mywiki.wooledge.org/BashPitfalls#set_-euo_pipefail). – pjh Aug 08 '23 at 16:09
  • @pjh interesting... the unfortunate part about this is that if I were to put `parent_function` in its own `.bash` file with the same `set -e` then it would all work as expected, it's only when it is all in one file the disabling of `set -e` has a noticeable impact. Is there another way to get the failure code of a failed function without modifying the failed function? – balupton Aug 08 '23 at 16:22
  • 1
    @balupton, see [What is the proper way to detect shell exit code when errexit option set](https://stackoverflow.com/q/9629710/4154375). – pjh Aug 08 '23 at 16:27
  • Thank you, I also found https://stackoverflow.com/a/70992989/130638 and https://unix.stackexchange.com/q/447519/50703 — reviewing them now. – balupton Aug 08 '23 at 16:29
  • Okay, came up with a solution that didn't require modifying anything but the conditional, thank you all for your help: https://stackoverflow.com/a/76861665/130638 – balupton Aug 08 '23 at 16:51
  • 1
    Simple solution: do not use `set -e`, it is a broken concept. – sorpigal Aug 08 '23 at 21:37

1 Answers1

1

Thanks to the commenters:

And these supporting resources:

I created this workaround for the question's code sample using the subshell technique:

#!/usr/bin/env bash

set -e

function child_function {
    return 1
}
function parent_function {
    child_function
    echo "parent noticed child exit code as $?"
}
function grandparent_function {
    local ec=0
    set +e; (set -e; parent_function); ec="$?"; set -e
    echo "grandparent noticed parent exit code as $ec"
}

grandparent_function

However, the subshell technique prevents side effects, as modifications do not escape the subshell.

I spent a few more days on this issue, and created a gist with various techniques, including one that works with side effects:

https://gist.github.com/balupton/21ded5cefc26dc20833e6ed606209e1b

balupton
  • 47,113
  • 32
  • 131
  • 182