与 PIPE 一起使用的 Bash 退出状态

与 PIPE 一起使用的 Bash 退出状态

我试图了解使用管道时如何传达退出状态。假设我用来which定位一个不存在的程序:

which lss
echo $?
1

由于which未能找到,lss我的退出状态为 1。这很好。但是,当我尝试以下操作时:

which lss | echo $?
0

这表明最后执行的命令已经正常退出。我能理解这一点的唯一方法是,也许 PIPE 也会产生退出状态。这是正确的理解方式吗?

答案1

管道的退出状态是右侧命令的退出状态。左侧命令的退出状态将被忽略。

(请注意,which lss | echo $?不会显示此内容。您将运行which lss | true; echo $?以显示此内容。在 中which lss | echo $?echo $?报告此管道之前的最后一个命令的状态。)

shell 这样做的原因是,有一种相当常见的情况,即应忽略左侧的错误。如果右侧退出(或更一般地关闭其标准输入)而左侧仍在写入,则左侧会收到 SIGPIPE 信号。在这种情况下,通常没有什么问题:右侧不关心数据;右侧不关心数据;右侧不关心数据。如果左侧的作用只是生成这些数据,那么它可以停止。

但是,如果左侧由于 SIGPIPE 之外的某种原因而死亡,或者如果左侧的工作不仅仅是在标准输出上生成数据,那么左侧的错误就是真正的错误,应该报告。

在普通 sh 中,唯一的解决方案是使用命名管道。

set -e
mkfifo p
command1 >p & pid1=$!
command2 <p
wait $pid1

在 ksh、bash 和 zsh 中,如果管道的任何组件以非零状态退出,您可以指示 shell 以非零状态退出管道。您需要设置pipefail选项:

  • 克什:set -o pipefail
  • 重击:shopt -s pipefail
  • zsh:(setopt pipefailsetopt pipe_fail

PIPESTATUS在 mksh、bash 和 zsh 中,您可以使用变量(bash、mksh) 或(zsh)获取管道中每个组件的状态,变量pipestatus是一个数组,其中包含最后一个管道中所有命令的状态(一般化$?)。

答案2

管道的退出状态是管道中最后一个命令的退出状态(除非pipefail在支持它的 shell 中设置了 shell 选项,在这种情况下,退出状态将是管道中最后一个命令退出的状态)非零状态)。

无法输出管道的退出状态从内部管道,因为在管道实际完成执行之前无法知道该值是什么。

管道

which lss | echo $?

是无意义的,因为echo不读取其标准输入(管道用于在一个命令的输出到下一个命令的输入之间传递数据)。也不会echo打印管道的退出状态,而是立即运行命令的退出状态管道。

$ false
$ which hello | echo $?
1
which: hello: Command not found.

$ true
$ which hello | echo $?
0
which: hello: Command not found.

这是一个更好的例子:

$ echo hello | read a
$ echo $?
0

$ echo nonexistent | { read filename && rm $filename; }
rm: nonexistent: No such file or directory
$ echo $?
1

这意味着管道也可以与以下各项一起使用if

if gzip -dc file.gz | grep -q 'something'; then
  echo 'something was found'
fi

答案3

是的,PIPE 产生退出状态;如果您想要 PIPE 之前的命令退出代码,您可以使用$PIPESTATUS

根据这个堆栈溢出问答,要在使用管道时打印命令的退出状态,您可以执行以下操作:

your_command | echo $PIPESTATUS

或者使用命令设置 pipelinefail set -o pipefail(要取消设置,请执行set +o pipefail),然后使用$?代替$PIPESTATUS

your_command | echo $?

这些命令只有在您至少运行一次后才有效;这意味着PIPESTATUS仅当您在使用管道的命令之后运行它时才有效,如下所示:

command_which_exit_10 | command_which_exit_1
echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"

您将获得 10 分${PIPESTATUS[0]}和 1 分${PIPESTATUS[1]}。看本 U&L 问答了解更多信息。

答案4

shell 在执行管道之前评估命令行。$?管道之前执行的最后一个命令的值也是如此。其echo本身是在之前还是之后启动是which无关紧要的。

现在,如果您的问题具体是关于退出状态的传播,您可以像这样研究默认行为:

% false | true
% echo $?
0

% true | false
% echo $?
1

如您所见,管道的退出状态是其最后一个命令的退出状态。

相关内容