我相信这是不可能的

我相信这是不可能的

考虑以下:

command1 | command2 | command3

据我了解,无论可能发生任何错误,管道都会运行每个命令。当命令返回 stderr 时,它不会通过管道传递到下一个命令,但下一个命令仍会运行(除非您使用|&)。我想要任何可能发生的错误来终止管道的其余部分。我认为set -o pipefail可以实现这一点,但如果管道中的任何内容失败,它只是终止管道之后可能出现的任何内容,即:

(set -o pipefail; cmd1 | cmd2 && echo "I won't run if any of the previous commands fail")

那么,最简​​洁的方式终止休息如果任何命令失败,管道会发生什么情况?我还需要它以失败命令的正确 stderr 退出。我是从命令行上下文而不是 shell 脚本执行此操作,因此我寻求简洁性。想法?

答案1

我相信这是不可能的

我相信,由于管道的执行方式,您所要求的并不是直接可能的。当 shell 在管道中执行“稍后”命令时,它不知道命令的成功或失败(返回值)。它实际上是同时运行所有这些,然后收集结果。

有一些解决方法可能会有所帮助。

解决方法1

一次执行一个命令并缓存结果 这更好,因为如果先前的命令失败,后面的命令绝对不会运行。

一个非常短的脚本示例:

cache_file=`tempfile`
if command1 > $cache_file ; then
    command2 < $cache_file
fi
rm $cache_file

解决方法2

执行一切但检查返回结果 无论如何,这仍然会运行所有命令,但它确实可以让您回过头来查找原因。

这里每个命令的 STDERR 都被重定向到不同的文件,扩展名为2>.然后PIPESTATUS检查以查找每个命令的返回代码。

command1 2> command1_err.log | command2 2> command2_err.log
for result in ${PIPESTATUS[@]} ; do
    if [ $result -ne 0 ] ; then
        echo command failed
    fi
done

在 shell 中运行管道的简要概述

要创建管道,shell 遵循以下步骤如同这些:

  1. 使用以下命令创建每个管道 ( |)管道()。每个管道都带有一个读句柄和一个写句柄。对于每个重新定向(<>),它打开相应的文件,使用该文件获取该文件的句柄打开()
  2. 外壳调用叉()管道中的每个命令启动一次新进程。
  3. 每个子进程将其 STDIN、STDOUT 和 STDERR 句柄交换为 (1.) 中创建的句柄。
  4. 假设该命令是一个外部二进制文件,每个子进程然后调用执行()加载并运行二进制文件。
  5. 然后父进程等待子进程完成使用等待()它还提供命令的返回值(成功或失败)。

答案2

停止管道的一种巧妙方法是中途杀死 shell。现在,这意味着终止您的交互式 shell(如果您从终端模拟器运行它),因此您必须启动另一个 shell 才能继续您的工作。但这至少会停止以下管道命令。

command1 2> command1_error.log | awk -v status=$? -v pid=$$ '{if (status != 0) { system("kill -9 " pid) } else { print } }' | command2 ..

注意:如果您从终端/tty 运行它,这将使您注销

或者,如果你想阻止最后一个命令(例如创建文件的命令是管道中的最后一个命令),你可以使用xargs -r

command1 | command2 | xargs -r command3

如果前一个命令没有输出任何内容,该-r标志将阻止运行 command3。xargs

相关内容