最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

In bash versions prior to v4.4, why do ERR traps that return essentially become `return 0`? - Stack Overflow

programmeradmin4浏览0评论

This is the minimal reproducible code to show this behaviour:

#!/usr/bin/env bash

set -Ee
trap '
TRAP_STATUS=$?
printf "%s\n" "TRAPPED: ${FUNCNAME-}: $TRAP_STATUS"
if [[ -n ${FUNCNAME-} ]]; then
    return $(($TRAP_STATUS * 10))
else
    exit $(($TRAP_STATUS * 10))
fi' ERR

function __return {
    return "$1"
}
function fn {
    printf '%s\n' 'fn'
    __return 1
    printf '%s\n' "inner: $?"
}

printf '%s\n' 'root'
fn
printf '%s\n' "root: $?"

exit 9

In bash versions prior to v4.4, such as 3.2.57, 4.1, 4.2.53, 4.3.30, the output is:

root
fn
TRAPPED: fn: 1
root: 0

With exit status 9.

On bash version 4.4.18 and higher, the output is:

root
fn
TRAPPED: fn: 1
TRAPPED: : 10

With exit status 100

What changed or was fixed in bash v4.4 to change this behaviour? Can anything be done for earlier versions to provide a consistent trap behaviour?

This is a contrived example from a much more complicated workflow. I understand that || return is more reliable than errexit.


This more comprehensive example better shows how this ERR trap behaviour in bash versions below v4.4 causes unwanted continuations:

#!/usr/bin/env bash

set -Ee
trap '
TRAP_STATUS=$?
printf "%s\n" "TRAPPED: ${FUNCNAME-}: $TRAP_STATUS"
if [[ ${FUNCNAME-} == "outer" ]]; then
    return 0
elif [[ -n ${FUNCNAME-} ]]; then
    return $(($TRAP_STATUS + 1))
else
    exit $(($TRAP_STATUS + 1))
fi' ERR

function __return {
    return "$1"
}
function inner {
    __return 10 # bash v4.4 completes here
    printf '%s\n' "inner __return (1/2): $?"
    __return 20
    printf '%s\n' "inner __return (2/2): $?"
    return 30
}
function outer {
    inner # bash v4.4 completes here
    printf '%s\n' "outer inner (1/2): $?"
    inner
    printf '%s\n' "outer inner (2/2): $?"
    __return 40
    printf '%s\n' "outer __return (1/2): $?"
    __return 50
    printf '%s\n' "outer __return (2/2): $?"
    return 60
}
function main {
    outer
    printf '%s\n' "main outer (1/2): $?"
    outer
    printf '%s\n' "main outer (2/2): $?"
    __return 70 # bash v4.4 completes here as our ERR trap wishes to ignore outer failures
    printf '%s\n' "main __return (1/2): $?"
    __return 80
    printf '%s\n' "main __return (2/2): $?"
    return 90
}

printf '%s\n' 'root'
main # bash v4.4+ completes here with exit status 72
printf '%s\n' "root: $?"
exit 123 # bash v4.3 and below completes here with exit status 123

Bash versions below v4.4:

root
TRAPPED: inner: 10
outer inner (1/2): 0
TRAPPED: inner: 10
outer inner (2/2): 0
TRAPPED: outer: 40
main outer (1/2): 0
TRAPPED: inner: 10
outer inner (1/2): 0
TRAPPED: inner: 10
outer inner (2/2): 0
TRAPPED: outer: 40
main outer (2/2): 0
TRAPPED: main: 70
root: 0
[123]

Bash versions v4.4 and above:

root
TRAPPED: inner: 10
TRAPPED: outer: 11
main outer (1/2): 0
TRAPPED: inner: 10
TRAPPED: outer: 11
main outer (2/2): 0
TRAPPED: main: 70
TRAPPED: : 71
[72]

I have already checked the changelog for anything that could be relevant, and was not able to find anything. These seem tangential, but do not seem to apply to this specific issue:

  • c. Fixed trap handling code so traps don't inherit a command's temporary environment.
    • this isn't about environment
  • b. Fixed a bug that caused subshells to free trap strings associated with inherited signals.
    • this isn't applicable, as this is about subshells, and our example shows the issue even without subshells
  • l. Fixed a bug that caused `bash -c' to not run a trap specified in the command string.
    • the example doesn't use bash -c
  • m. Fixed a bug where `return' used the wrong exit status when executed in a DEBUG trap.
    • the example doesn't use the DEBUG trap
  • o. Fixed a bug that caused set -e to be honored in cases of builtins invoking other builtins when it should be ignored.
    • the example doesn't use builtins, removing printf has no effect
  • z. Fixed a bug that caused a builtin command containing a process substitution to return the wrong exit status.
    • the example doesn't use builtins nor process substitution, removing printf has no effect
  • b. inherit_errexit: a new `shopt' option that, when set, causes command substitutions to inherit the -e option. By default, those subshells disable -e. It's enabled as part of turning on posix mode.
    • the example doesn't use inherit_errexit, nor does using it have any effect
  • f. The shell now undoes redirections before exiting the shell when the `-e' option is enabled, and a shell function fails.
    • the example doesn't use redirections or depend on them
  • k. Fixed a bug that caused the ERR and RETURN traps to be unset if they were set in a shell function but unset previously.
    • the example never unset the traps, so this condition does not apply, furthermore we can see that the trap persists and functions it's just that its behaviour is not expected
  • p. Fixed a bug that caused the shell to crash when running a trap containing a process substitution.
    • the example doesn't use process substitution
  • ll. $BASH_SUBSHELL now has more consistent values in asynchronous simple commands.
    • the example doesn't use $BASH_SUBSHELL
  • f. Fixed a bug that caused `return' executed from a trap handler to use the wrong return status when one was not supplied as an argument.
    • the example always passes an argument to return
  • jj. Asynchronous commands now always set $? to 0 and are not affected by whether or not the command's exit status is being inverted.
    • the example doesn't use an asynchronous command
  • rrr. Fixed a bug that caused some of the shell's internal traps (e.g., ERR) to be interrupted (and leave incorrect state) by pending SIGINTs.
    • the example isn't receiving a SIGINT
  • xxx. Fixed a bug that caused the ERR trap in a shell function to have the wrong value for $LINENO.
    • the example isn't using $LINENO
  • eeee. If the -T option is not set, allow the source builtin and shell functions to set a DEBUG trap that persists after the sourced file or function returns, instead of restoring the old (unset) value unconditionally.
    • the example isn't using DEBUG
  • oooo. Fixed a bug that caused the shell to not enable and disable function tracing with changes to the `extdebug' shell option.
    • the example isn't altering extdebug

This is the minimal reproducible code to show this behaviour:

#!/usr/bin/env bash

set -Ee
trap '
TRAP_STATUS=$?
printf "%s\n" "TRAPPED: ${FUNCNAME-}: $TRAP_STATUS"
if [[ -n ${FUNCNAME-} ]]; then
    return $(($TRAP_STATUS * 10))
else
    exit $(($TRAP_STATUS * 10))
fi' ERR

function __return {
    return "$1"
}
function fn {
    printf '%s\n' 'fn'
    __return 1
    printf '%s\n' "inner: $?"
}

printf '%s\n' 'root'
fn
printf '%s\n' "root: $?"

exit 9

In bash versions prior to v4.4, such as 3.2.57, 4.1, 4.2.53, 4.3.30, the output is:

root
fn
TRAPPED: fn: 1
root: 0

With exit status 9.

On bash version 4.4.18 and higher, the output is:

root
fn
TRAPPED: fn: 1
TRAPPED: : 10

With exit status 100

What changed or was fixed in bash v4.4 to change this behaviour? Can anything be done for earlier versions to provide a consistent trap behaviour?

This is a contrived example from a much more complicated workflow. I understand that || return is more reliable than errexit.


This more comprehensive example better shows how this ERR trap behaviour in bash versions below v4.4 causes unwanted continuations:

#!/usr/bin/env bash

set -Ee
trap '
TRAP_STATUS=$?
printf "%s\n" "TRAPPED: ${FUNCNAME-}: $TRAP_STATUS"
if [[ ${FUNCNAME-} == "outer" ]]; then
    return 0
elif [[ -n ${FUNCNAME-} ]]; then
    return $(($TRAP_STATUS + 1))
else
    exit $(($TRAP_STATUS + 1))
fi' ERR

function __return {
    return "$1"
}
function inner {
    __return 10 # bash v4.4 completes here
    printf '%s\n' "inner __return (1/2): $?"
    __return 20
    printf '%s\n' "inner __return (2/2): $?"
    return 30
}
function outer {
    inner # bash v4.4 completes here
    printf '%s\n' "outer inner (1/2): $?"
    inner
    printf '%s\n' "outer inner (2/2): $?"
    __return 40
    printf '%s\n' "outer __return (1/2): $?"
    __return 50
    printf '%s\n' "outer __return (2/2): $?"
    return 60
}
function main {
    outer
    printf '%s\n' "main outer (1/2): $?"
    outer
    printf '%s\n' "main outer (2/2): $?"
    __return 70 # bash v4.4 completes here as our ERR trap wishes to ignore outer failures
    printf '%s\n' "main __return (1/2): $?"
    __return 80
    printf '%s\n' "main __return (2/2): $?"
    return 90
}

printf '%s\n' 'root'
main # bash v4.4+ completes here with exit status 72
printf '%s\n' "root: $?"
exit 123 # bash v4.3 and below completes here with exit status 123

Bash versions below v4.4:

root
TRAPPED: inner: 10
outer inner (1/2): 0
TRAPPED: inner: 10
outer inner (2/2): 0
TRAPPED: outer: 40
main outer (1/2): 0
TRAPPED: inner: 10
outer inner (1/2): 0
TRAPPED: inner: 10
outer inner (2/2): 0
TRAPPED: outer: 40
main outer (2/2): 0
TRAPPED: main: 70
root: 0
[123]

Bash versions v4.4 and above:

root
TRAPPED: inner: 10
TRAPPED: outer: 11
main outer (1/2): 0
TRAPPED: inner: 10
TRAPPED: outer: 11
main outer (2/2): 0
TRAPPED: main: 70
TRAPPED: : 71
[72]

I have already checked the changelog for anything that could be relevant, and was not able to find anything. These seem tangential, but do not seem to apply to this specific issue:

  • c. Fixed trap handling code so traps don't inherit a command's temporary environment.
    • this isn't about environment
  • b. Fixed a bug that caused subshells to free trap strings associated with inherited signals.
    • this isn't applicable, as this is about subshells, and our example shows the issue even without subshells
  • l. Fixed a bug that caused `bash -c' to not run a trap specified in the command string.
    • the example doesn't use bash -c
  • m. Fixed a bug where `return' used the wrong exit status when executed in a DEBUG trap.
    • the example doesn't use the DEBUG trap
  • o. Fixed a bug that caused set -e to be honored in cases of builtins invoking other builtins when it should be ignored.
    • the example doesn't use builtins, removing printf has no effect
  • z. Fixed a bug that caused a builtin command containing a process substitution to return the wrong exit status.
    • the example doesn't use builtins nor process substitution, removing printf has no effect
  • b. inherit_errexit: a new `shopt' option that, when set, causes command substitutions to inherit the -e option. By default, those subshells disable -e. It's enabled as part of turning on posix mode.
    • the example doesn't use inherit_errexit, nor does using it have any effect
  • f. The shell now undoes redirections before exiting the shell when the `-e' option is enabled, and a shell function fails.
    • the example doesn't use redirections or depend on them
  • k. Fixed a bug that caused the ERR and RETURN traps to be unset if they were set in a shell function but unset previously.
    • the example never unset the traps, so this condition does not apply, furthermore we can see that the trap persists and functions it's just that its behaviour is not expected
  • p. Fixed a bug that caused the shell to crash when running a trap containing a process substitution.
    • the example doesn't use process substitution
  • ll. $BASH_SUBSHELL now has more consistent values in asynchronous simple commands.
    • the example doesn't use $BASH_SUBSHELL
  • f. Fixed a bug that caused `return' executed from a trap handler to use the wrong return status when one was not supplied as an argument.
    • the example always passes an argument to return
  • jj. Asynchronous commands now always set $? to 0 and are not affected by whether or not the command's exit status is being inverted.
    • the example doesn't use an asynchronous command
  • rrr. Fixed a bug that caused some of the shell's internal traps (e.g., ERR) to be interrupted (and leave incorrect state) by pending SIGINTs.
    • the example isn't receiving a SIGINT
  • xxx. Fixed a bug that caused the ERR trap in a shell function to have the wrong value for $LINENO.
    • the example isn't using $LINENO
  • eeee. If the -T option is not set, allow the source builtin and shell functions to set a DEBUG trap that persists after the sourced file or function returns, instead of restoring the old (unset) value unconditionally.
    • the example isn't using DEBUG
  • oooo. Fixed a bug that caused the shell to not enable and disable function tracing with changes to the `extdebug' shell option.
    • the example isn't altering extdebug
Share Improve this question edited Mar 9 at 8:04 balupton asked Mar 9 at 3:27 baluptonbalupton 48.8k32 gold badges134 silver badges184 bronze badges 5
  • "What changed or was fixed in bash v4.4 to change this behaviour?" - There is a CHANGES file for bash; see git.savannah.gnu./cgit/bash.git/tree/CHANGES – Stephen C Commented Mar 9 at 4:04
  • @StephenC yes, I've gone through that, and cannot find anything in there that would explain that. Hence my question. – balupton Commented Mar 9 at 4:12
  • You should add your research findings (including negative ones, like the CHANGES file) to the question ... so that others don't unwittingly spend time repeating your work. – Stephen C Commented Mar 9 at 4:21
  • bug report lists.gnu./archive/html/bug-bash/2016-02/msg00140.html fix git.savannah.gnu./cgit/bash.git/diff/… took me 5 seconds to find these hence the -1 – oguz ismail Commented Mar 9 at 5:25
  • @oguzismail already evaluated that and it is not related, there is no subshell in my example. Note how they use () in their example, not {}. – balupton Commented Mar 9 at 7:38
Add a comment  | 

1 Answer 1

Reset to default 3

This bug fix altered the behaviour:

_run_trap_internal: make sure to catch and use return values supplied as arguments to return; instead of just catching return, make sure we use return_catch_value as well. Fixes bug reported by Grisha Levit; affects RETURN, DEBUG, ERROR traps.

       if (sig != DEBUG_TRAP && sig != RETURN_TRAP && sig != ERROR_TRAP)
  flags |= SEVAL_RESETLINE;
       if (function_code == 0)
- parse_and_execute (trap_command, tag, flags);
-
-      trap_exit_value = last_command_exit_value;
+        {
+   parse_and_execute (trap_command, tag, flags);
+   trap_exit_value = last_command_exit_value;
+        }
+      else
+        trap_exit_value = return_catch_value;
 
 #if defined (JOB_CONTROL)
       if (sig != DEBUG_TRAP) /* run_debug_trap does this */

It is unlikely that anything be done for earlier versions to provide a consistent trap behaviour, because it's a code change.

If you absolutely need an earlier version, try to rebuild that version with this change.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论