使用(...)创建的子shell与bash -c'...'的行为不同

anu*_*ava 5 unix bash shell

我在命令行中使用set -e内部(...)但在执行命令时它不会退出子shell false.

代码1:

( set -e; echo "$BASHPID: start"; false; echo "foobar"; date; ) && 
  echo "$BASHPID: ok" || echo "$BASHPID: nope"
9136: start
foobar
Wed, Apr 29, 2015  7:14:24 PM
7292: ok
Run Code Online (Sandbox Code Playgroud)

但是,如果我bash -c用于子shell创建,那么它的行为与我期望的一样.

代码2:

bash -c 'set -e; echo "$BASHPID: start"; false; echo "foobar"; date;' &&
  echo "$BASHPID: ok" || echo "$BASHPID: nope"
7880: start
7292: nope
Run Code Online (Sandbox Code Playgroud)

有趣的是,如果我删除&&||在子壳后分开,那么(...)也表现得很好.

代码3:

( set -e; echo "$BASHPID: start"; false; echo "foobar"; date; )
5940: start
Run Code Online (Sandbox Code Playgroud)

所以我得到的结论是:

  1. (...) 表现不同于 bash -c
  2. (...) && false表现不同于(...)和改变子shell的行为.

我在解释这种奇怪的(至少对我而言)行为时犯了一些明显的错误吗?

Eta*_*ner 2

set -ewith的行为&&是有意的。

当命令是&&序列的一部分时,它明确不会触发。

来自 POSIX 规范:

-e

当此选项打开时,当任何命令失败时(由于 Shell 错误的后果中列出的任何原因或返回大于零的退出状态),shell 应立即退出,但以下例外:多命令管道不应导致 shell 退出。仅考虑管道本身的故障。

当执行 while、until、if 或 elif 保留字(以 ! 开头的管道)后面的复合列表时,应忽略 -e 设置。保留字,或 AND-OR 列表中除最后一个以外的任何命令。

如果子 shell 命令以外的复合命令的退出状态是在 -e 被忽略时失败的结果,则 -e 不适用于该命令。

此要求分别适用于 shell 环境和每个子 shell 环境。例如,在:

set -e; (false; echo one) | cat; echo two
Run Code Online (Sandbox Code Playgroud)

false 命令导致子 shell 退出而不执行 echo one;然而,执行 echo 2 是因为管道的退出状态 (false; echo one) | 猫为零。

我认为这里的区别在于 shell 是否知道这个事实。

(...)可能是因为当前 shell 执行它,也bash -c可能不是,因为那是一个完全外部的进程(或其他进程)。

最后一点是猜测,在我看来,这里的子 shell 行为完全没有帮助并且不可靠set -e(这就是为什么许多人建议避免它)。

尽管如果显式运行的 shell 不存在脚本组合问题,那么这似乎也可能为该可靠性问题提供一种“逃生舱口”。