bash `set -e` reset inside functions when run via `$(...)`?

Viewed 211

It seems that the set -e option in Bash gets reset inside of functions, when those functions are invoked via a $(...) expansion.

This surprises me, and I'm not sure if it is a bug or not. I have not been able to find a description of this behavior in the (usually quite thorough) Bash manpage.

Note: here are some other similar SO posts:

But neither of them deals with $(...), which is not really discussed in the manpage either.

I also cannot find reference to this issue in the excellent Bash FAQ 105.

Here is a small program to demonstrate the issue:

echo "Initial:    $-"
set -eu
echo "After set:  $-"

function foo() {
    echo "Inside foo: $-"
}
foo

function bar() {
    false    # I'd expect this to immediately fail
    echo "Inside bar: $-"
}
# When a $(...) construct is involved, 'bar' runs to completion!
x=$(bar)
echo "We should never get here ... but we do."
echo "$x"

For me, on Bash version 5.0.11(0)-release, I get the following output:

Initial:    hB
After set:  ehuB
Inside foo: ehuB
We should never get here ... but we do.
Inside bar: huB

So, as you can see, the -u option does get "passed through" to the function in all cases. And the -e option gets passed through when the function is called normally. But only in the special case of $(bar) does the -e option get reset.

Does anyone know if this is documented behavior, or otherwise explainable?

It makes no sense to me (:

2 Answers

The behaviour of set -e in conjunction with Command Substitution is documented in Command Execution Environment:

Subshells spawned to execute command substitutions inherit the value of the -e option from the parent shell. When not in POSIX mode, Bash clears the -e option in such subshells.

That seems to say that the behaviour you see is expected — unless you're running in POSIX mode, the -e option is unset in command substitution subshells in Bash (even though the -e setting is initially inherited, it is changed soon after the subshell commences execution). It's a funny way of writing it, though.

The relevant man page quotes for you, first from the Command Substiution section.

Command substitution allows the output of a command to replace the command name. There are two forms:

         $(command)
   or
         `command`

Bash performs the expansion by executing command in a subshell environment and replacing the command substitution with the standard output of the command, with any trailing newlines deleted.

And from Command Execution Environment:

Subshells spawned to execute command substitutions inherit the value of the -e option from the parent shell. When not in posix mode, bash clears the -e option in such subshells.

So bar get executed in a subshell, and since your are not in posix mode, the -e option gets cleared.

Add set -o posix to the start of you script and it will behave as expected, although expect other differences when using this mode.

Related