为什么 bash 子 shell 中的 EXIT 陷阱并不总是被调用?

onl*_*one 5 bash shell rvm

我在 bash 和子EXITshell 内发现了一些奇怪的行为。我希望以下四行都输出相同的内容(“嗨被困”):

a=$(trap 'echo trapped' EXIT ; echo hi); echo $a
a=$(trap 'echo trapped' EXIT && echo hi); echo $a
a=$(trap 'echo trapped' EXIT ; /bin/echo hi); echo $a
a=$(trap 'echo trapped' EXIT && /bin/echo hi); echo $a
Run Code Online (Sandbox Code Playgroud)

前三个确实打印了“hi trap”,但最后一个不是。它只是输出“hi”。陷阱没有被调用。您可以通过以下方式验证这一点set -x

set -x; a=$(trap 'echo trapped' EXIT ; echo hi); set +x; echo $a
set -x; a=$(trap 'echo trapped' EXIT && echo hi); set +x; echo $a
set -x; a=$(trap 'echo trapped' EXIT ; /bin/echo hi); set +x; echo $a
set -x; a=$(trap 'echo trapped' EXIT && /bin/echo hi); set +x; echo $a
Run Code Online (Sandbox Code Playgroud)

通过一些试验和错误,我发现EXIT在以下条件下不会调用陷阱:

  1. 整个子 shell 程序是一个用 链接在一起的命令列表&&
    • 如果您使用;, 或什至||在任何时候,陷阱都会执行。
  2. 链中的所有命令都必须执行。
    • 如果任何一个命令(最后一个命令除外)以非零退出状态退出,使得最后一个命令永远不会执行,则陷阱将执行。
  3. 最终命令必须是系统上的程序,而不是内置的 shell,也不是函数。
    • 非最终命令可以是内置命令或函数,只要最终命令是程序,陷阱就不会运行

这是故意的吗?有记录吗?

作为参考,我遇到了这个,因为rvm用它自己的函数覆盖cd,最终添加了一个陷阱,其中EXITdoes (除其他外)echo -n 'Saving session...'。我正在运行一个使用以下 bash 习惯用法的shell 脚本:

some_dir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )
Run Code Online (Sandbox Code Playgroud)

因此some_dir附加了“正在保存会话...”。调试起来很困难,因为子 shell 并不总是运行EXITrvm 添加的陷阱。

Bao*_*jun 3

我曾经strace -e clone,execve -f -p $$&在运行 echo 版本和 /bin/echo 版本时查看当前 shell 在做什么。我放了一个&,让它继续读取命令。

在 /bin/echo 版本中,我相信 bash 做了一个快捷方式并执行了 /bin/echo 的 () 子 shell,因此陷阱不再存在(我猜陷阱不会在 execve 中存活)。

在裸echo版本中,它是一个内置的shell,因此不需要execve,因此当前的()子shell作为shell退出,并调用trap。

现在,另一个奇怪的事情是,如果我这样做:bash -c 'a=$(trap "echo trapped" EXIT && /bin/echo hi); echo $a',你会看到它被困住了!

我想这是因为 bash 仅在交互模式下使用快捷方式。批处理模式和交互模式之间的另一个示例差异是for x in $(seq 1 30); sleep 1; done。如果您在终端中输入它,然后立即按 Cz,然后使用fg将其返回,您将看到它将立即退出 - 剩余的睡眠将被跳过。如果将其放入脚本中,并且 Cz、fg,它将继续休眠以完成剩余的循环。