我试图了解使用管道时如何传达退出状态。假设我用来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 pipefail
或setopt 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
如您所见,管道的退出状态是其最后一个命令的退出状态。