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

时间:2018-06-06 20:27:27

标签: bash shell rvm

我在bash中看到一些奇怪的行为,并在子shell中捕获EXIT。我希望以下四行都输出相同的东西(“喜被困”):

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”。没有调用陷阱。您可以使用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. 整个子shell程序是与&&链接在一起的命令列表。
    • 如果您在任何时候使用;甚至||,则会执行陷阱。
  2. 链中的所有命令都必须执行。
    • 如果任何一个命令(除了最后一个)以非零退出状态退出,使得最后一个命令永远不会执行,则陷阱将执行。
  3. 最终命令必须是系统上的程序,而不是内置shell而不是函数。
    • 非最终命令可以是内置函数或函数,只要最终命令是程序,陷阱就不会运行
  4. 这是故意的吗?有记录吗?

    作为参考,我偶然发现了这一点,因为rvm使用自己的函数覆盖cd,最终在EXIT上添加了一个陷阱(其中包括)echo -n 'Saving session...' 。我正在运行uses this bash idiom

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

    所以some_dir附加了“保存会话...”。它很难调试,因为子shell并不总是运行rvm添加的EXIT陷阱。

1 个答案:

答案 0 :(得分:0)

我使用strace -e clone,execve -f -p $$&来查看运行回显版本和/ bin / echo版本时当前shell的行为。我放置了&,以便它将继续读取命令。

在/ bin / echo版本中,我相信bash做了一个快捷方式并执行了/ bin / echo的()子外壳,因此陷阱不再存在(我想陷阱无法在execve中生存)。 / p>

在裸回显版本中,它是内置的shell,因此无需执行,因此,当前()子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。如果您在终端中输入它,然后立即按C-z,然后使用fg将其恢复,您将看到它会立即退出-其余的睡眠将被跳过。如果将其放在脚本中,然后将C-z,fg放入脚本中,则它将在其余循环中继续休眠。