【问题标题】:Can I stop later parts of a pipeline from running if an earlier part failed?如果早期部分失败,我可以阻止管道的后续部分运行吗?
【发布时间】:2026-02-14 21:30:01
【问题描述】:

我有一个管道命令,例如:

set -euxo pipefail

echo 'hello' | foo | touch example.sh

这是输出:

$ set -euxo pipefail
$ echo hello
$ foo
$ touch example.sh
pipefail.sh: line 4: foo: command not found

我认为set -e 会导致脚本退出。但即使foo 无法识别,脚本仍在执行touch 命令。如果foo 失败,我如何让它退出?

【问题讨论】:

  • 管道的所有组件同时发生。您的代码停止执行管道之后的任何额外行。它无法阻止其他流水线阶段的运行。
  • 那么失败就没有办法退出了吗?
  • 想想管道的作用:echo hello | foo | touch example.sh 将运行echo 的子shell 的标准输出连接到将使用exec 将自身替换为foo 的子shell 的标准输入,和foo 的标准输出到子shell 的标准输入,它将用touch 替换自己。然后,建立这些连接之后,它会启动所有三个程序并并行运行它们。显然,在您启动程序之前,您无法判断程序是否会失败。
  • 现在,如果你有echo hello | foo | cat; touch example.sh那个会按照你的预期行事。 (cat 只是一个占位符,表明foo 不需要在尾部位置)。
  • 好的。如果命令是这样的:gunzip file1 | gzip文件1?第一个管道的结果被传递到第二个管道。这两个不是同时发生的吧?这是我的真实用例。我们可能会收到一个常规的 .txt 文件,但程序没有退出。

标签: bash shell pipe


【解决方案1】:

您无法真正想到具有“较早”或“较晚”部分的管道,除非数据通过它们从一端移动到另一端:管道的所有部分同时运行.

因此,如果较早的部分失败,您无法阻止后面的部分启动,因为较晚的部分与较早的部分同时开始


如上所述,有 机制允许管道在发生故障时提前关闭——这些机制以相同的方式工作,无需设置任何非默认 shell 标志完全没有:

  • 如果您使用设计用于管道右侧的工具(与touch 不同),它将从标准输入读取 - 因此如果组件到左边失败。
  • 如果您使用设计用于管道左侧的工具,如果右侧的东西不再运行,它将在尝试写入时收到SIGPIPE

当然,如果您从一个不写入标准输出的程序或一个不从标准输入读取的程序进行管道传输,这些机制就不起作用——但这样的程序没有多大意义无论如何都可以在管道中使用。

【讨论】:

  • 好的,这是真正的用例:gunzip file.gz | awk '{gsub("'"$ctrl_m"'","")} | gzip > file.gz 如果我将 .txt 文件传递​​到这一行,第一个 gunzip 命令将失败,但管道的其余部分将继续。在这种情况下,有没有办法提前关闭管道?
  • 你不能使用相同的文件名作为源和目标,如果你不使用 -c 或从标准输入提供输入,gunzip 将直接创建一个输出文件本身,根本不写入管道。 gunzip -c file1.gz | awk '...' | gzip >file2.gz 将工作。无论哪种方式,您都会创建您的 file2.gz,但如果管道失败,您可以将其删除。
  • 也就是说:filename="file.gz"; set -o pipefail; if gunzip -c <"$filename" | awk '...' | gzip >"$filename.tmp.$$"; then mv -- "$filename.tmp.$$" "$filename"; else echo "ERROR: Failure in pipeline" >&2; exit 1; fi -- 如果你需要在其他用户可以安全写入的目录中操作,请改用mktemp创建你的临时文件,从而避免符号链接攻击。请注意,您不能依赖 set -e 在这里进行正确处理,我们有意通过将错误放入 if 内的“已检查”位置来防止它运行。