On Thu, Sep 24, 2026 at 08:10:30PM PDT, Martin D Kealey wrote:
The significant change isn't any combination of «set -e», «set -o errexit»
and/or «shopt -s execfail», but rather the insertion of «exec».

The "exit on failure" is one of the minor differences between an
interactive shell and a non-interactive one, including a subshell of an
interactive shell

It is sufficiently demonstrated by:

( exec /no/such/path || echo In Subshell )
exec /no/such/path || In Interactive Shell

without any use of set-e, errexit, or execfail.

-Martin


Sorry, I don't see how interactive shells enter the picture here; all of the examples I provided were of non-interactive shells.

The documentation of the 'execfail' option says:

If set, a non-interactive shell will not exit if it cannot execute the file specified as an argument to the
exec builtin.

So when I enable it, I'd expect that to mean that the shell would continue running after a failed exec. And without 'set -e' (a.k.a. errexit), it does:

$ bash -c 'shopt -s execfail; exec /bin/enoent; echo foo'
bash: line 1: /bin/enoent: No such file or directory
foo

And yes, with 'set -e' enabled, it is expected that the shell exits immediately when a command fails, so this is expected behavior as well:

$ bash -e -c 'shopt -s execfail; exec /bin/enoent; echo not printed'
bash: line 1: /bin/enoent: No such file or directory

However, as an exception to that immediate-exit behavior, the documentation for errexit says:

The shell does not exit if the command that fails is
[...] part of the test following the if or elif reserved words [...]

Given that, I would expect the following to print "failed":

$ bash -e -c 'shopt -s execfail; if ! exec /bin/enoent; then echo failed; fi'
bash: line 1: /bin/enoent: No such file or directory

But instead it exits immediately after the failed exec, specifically via the exit_immediately_on_error path in report_error() in error.c.

I'm not certain if this entirely an appropriate fix, but it seems to pass 'make test':

diff --git execute_cmd.c execute_cmd.c
index 6a22395e0cde..42bb4f71dd2a 100644
--- execute_cmd.c
+++ execute_cmd.c
@@ -4997,8 +4997,9 @@ execute_builtin (sh_builtin_func_t *builtin, WORD_LIST 
*words, int flags, int su
      value of the command, we turn the -e flag off ourselves and disable
      the ERR trap, then restore them when the command completes.  This is
      also a problem (as below) for the command and source/. builtins. */
+  extern int no_exit_on_failed_exec;
   if (subshell == 0 && (flags & CMD_IGNORE_RETURN) &&
-       (builtin == eval_builtin || (flags & CMD_COMMAND_BUILTIN) || builtin == 
source_builtin))
+       (builtin == eval_builtin || (flags & CMD_COMMAND_BUILTIN) || builtin == 
source_builtin || (builtin == exec_builtin && no_exit_on_failed_exec)))
     {
       begin_unwind_frame ("eval_builtin");
       unwind_protect_int (exit_immediately_on_error);



With that patch applied, I see the behavior I'd expect:

$ ./bash -e -c 'shopt -s execfail; exec /bin/enoent || echo foo'
./bash: line 1: /bin/enoent: No such file or directory
foo

It also lets a defined EXIT trap run:

$ ./bash -e -c 'trap "echo trapped" EXIT; shopt -s execfail; exec /bin/enoent 
|| :'
./bash: line 1: /bin/enoent: No such file or directory
trapped

Whereas with unmodified bash 5.3.0 the trap doesn't run:

$ bash -e -c 'trap "echo trapped" EXIT; shopt -s execfail; exec /bin/enoent || 
:'
bash: line 1: /bin/enoent: No such file or directory



Thanks,
Zev


Reply via email to