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

Why does the EXIT trap in bash subshells not always get called?

我发现 bash 出现了一些奇怪的行为,并且在 subshell 中捕获了 EXIT。我希望以下四行都输出相同的内容 ("hi trapped"):

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

前三个会打印 "hi trapped",但不会打印最后一个。它只是输出 "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

通过反复试验,我发现 EXIT 陷阱在以下情况下不会被调用:

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

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

作为参考,我遇到这个是因为 rvm overwrites cd with its own function that ends up adding a trap on EXIT which does (among other things) echo -n 'Saving session...'. I was running a shell script that uses this bash idiom:

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

所以 some_dir 得到 'Saving session...' 附加到它。很难调试,因为 subshells 并不总是 运行 正在添加 EXIT trap rvm。

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

在 /bin/echo 版本中,我相信 bash 做了一个快捷方式并为 /bin/echo 执行了 () subshell,所以陷阱不存在不再存在(陷阱不会在 execve 中存活,我猜)。

在bare echo版本中,它是一个shell builtin,所以不需要execve,所以current () subshell exit as a shell,trap是打电话。

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

我猜这是因为 bash 只在交互模式下做快捷方式。批处理模式和交互模式之间的另一个示例差异是 for x in $(seq 1 30); sleep 1; done。如果您在终端中输入它,并立即按 C-z,然后使用 fg 将其恢复,您会看到它会立即退出——剩余的睡眠将被跳过。如果你把它放在一个脚本中,然后 C-z, fg,它会在剩余的循环中继续休眠。