使用 && 进行管道化处理时,获取 bash 脚本不会出错



我发现获取以下 bash 脚本不会导致命令序列在使用&&进行管道化时停止。

sourceme.sh:

#!/usr/bin/env bash
set -o errexit
set -o | grep errexit
echo "About to error from sourceme.sh..."
$(false)
echo "sourceme.sh did not exit on error (exit code: $?)"

这是演示问题的调用。

(source sourceme.sh && echo "sourceme.sh did not error (exit code: $?)")

我从中得到的输出如下。

errexit         on
About to error from sourceme.sh...
sourceme.sh did not exit on error (exit code: 1)
sourceme.sh did not error (exit code: 0)

这与我预期的相反,我希望看到整个命令在sourceme.sh中达到$(false)时死亡,产生以下输出。

errexit         on
About to error from sourceme.sh...

继续奇怪的是,如果我使用;代替&&,那么我希望命令继续,但事实并非如此。

$ (source sourceme.sh && echo "sourceme.sh did not error (exit code: $?)")
errexit         on
About to error from sourceme.sh...

因此,在获取文件时,&&的行为符合我期望;的行为,;的行为与我希望&&的行为相同。

我对这应该如何工作的理解哪里出了问题?

粗略地说,这个想法是set -o errexit(或者,等效地,set -e)导致 shell 在未捕获的错误时退出。&&遵循source sourceme.sh的事实意味着捕获了错误。

因此,如果sourceecho命令与;连接,shell 将退出:

$ (source sourceme.sh ; echo "sourceme.sh did not error (exit code: $?)")
errexit         on
About to error from sourceme.sh...

但是,如果sourceecho命令与&&连接,则 shell 假定您已预见到source命令失败的可能性,并且 shell 不会退出:

$ (source sourceme.sh && echo "sourceme.sh did not error (exit code: $?)")
errexit         on
About to error from sourceme.sh...
sourceme.sh did not exit on error (exit code: 1)
sourceme.sh did not error (exit code: 0)

set -e的许多行为可能被认为是奇怪的或意外的。 有关更多示例,请参阅为什么设置 -e(或设置 -o errexit,或陷阱 ERR)不执行我预期的操作?。

文档

set -e的行为(errexit)在man bash中定义得更精确:

-e
如果管道(可能包含单个简单命令)、列表或复合命令,请立即退出(请参阅 SHELL 上面的语法),以非零状态退出。 外壳确实 如果失败的命令是命令列表的一部分,则不退出 紧随一段时间或直到关键字,测试的一部分 在 if 或 ELIF 保留字之后,执行的任何命令的一部分 在 && 或 ||列表,但最终 && 或 || 后面的命令除外, 管道中除最后一个命令之外的任何命令,或者如果命令返回 值正在与 ! 反转。 如果复合命令不是 subshell 返回非零状态,因为命令失败而 -e 被忽略,外壳不退出。 陷阱 如果设置了 ERR,则在外壳程序退出之前执行。 此选项适用 到外壳环境和每个子壳 环境单独(请参阅上面的命令执行环境), 并可能导致子壳在执行所有 子外壳中的命令。

如果化合物 命令或 shell 函数在 -e 所在的上下文中执行 忽略,在 复合命令或函数体将受到 -e 设置的影响, 即使设置了 -e 并且命令返回失败状态。 如果
复合命令或 shell 函数在 忽略 -e 的上下文,该设置将没有任何影响 直到复合命令或包含函数调用的命令 完成。

同义词

man bashhelp set中所述,以下两个命令是等效的:

set -e
set -o errexit

你观察到的是这个。

此命令不会在出错时退出:

(source sourceme.sh && echo "sourceme.sh did not error (exit code: $?)")

但是这个命令:

(bash sourceme.sh && echo "sourceme.sh did not error (exit code: $?)")

退出如此&&并不是这种怪异的唯一原因。正是source&&的结合使它无法退出。

您还注意到,使用;而不是&&会使其退出,如以下命令所示:

(bash sourceme.sh && echo "sourceme.sh did not error (exit code: $?)")

此行为实际上取决于正在使用的 bash 版本。

当我在OSX上运行此命令时bash 3.2.57(1)-release它确实退出

(source sourceme.sh && echo "sourceme.sh did not error (exit code: $?)")

errexit         on
About to error from sourceme.sh...

但是,在 bash 版本上,4.2及以上版本的行为会发生变化set -e在与&&一起使用时不会退出。

所以底线是set -e是非常不可靠的,应该避免依赖于它在重要的 shell 脚本中的行为。

最新更新