PIPE使用的Bash退出状态


10

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

which lss
echo $?
1

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

which lss | echo $?
0

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


2
管道的所有组件都同时运行。因此,在中which lss | echo $?,退出echo之前which已启动;生成命令行的shell echo无法知道以后的退出状态which
查尔斯·达菲

它们不会同时运行。管道的全部目的是将一个命令的输出重定向到另一个命令。Shell执行的最后一条命令(即语句中的最后一条命令)将是返回返回代码的命令。
Tim S.

3
@TimS。但是它们确实是同时运行的,如果这是一个运行时间很长的命令,这就是您可以在第一个命令完成之前获得输出的第一位的方法。
Random832

我想我正在考虑执行的开始-有一定的重叠,但是过程不是一起开始的。我明白你的意思了,我的错。(管道正在重定向输出,而不是执行...。)我使评论受了限制,但希望您知道我的意思。:)
Tim S.

是的,但是执行的开始并不重要,重要的是结束;最后一条命令在第一个命令结束之前开始
user371366 '21

Answers:


14

管道的退出状态是管道中最后一条命令的退出状态(除非在pipefail支持该命令的外壳程序中设置了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

10

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

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

外壳程序以这种方式运行的原因是,在相当普遍的情况下,应忽略左侧的错误。如果在左侧仍在写时右侧退出(或更通常是关闭其标准输入),则左侧会收到SIGPIPE信号。在这种情况下,通常没有任何错误:右侧不关心数据;如果左侧的作用仅仅是产生此数据,则可以停止它。

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

在纯文本格式中,唯一的解决方案是使用命名管道。

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

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

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

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


谢谢吉尔,我没有意识到这些复杂性。这确实为我进一步澄清了这一点。
sshekhar1980 '17

4

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

根据此Stack Overflow Q&A的说明,要在使用管道时打印命令的退出状态,可以执行以下操作:

your_command | echo $PIPESTATUS

或使用命令设置pipefail 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问答


2
尽管PIPESTATUS在最后一个管道中包含了命令的退出状态,但第一个示例的意义远没有somecmd | echo $?Kusalananda在其答案中处理过的有意义。false; true | echo $PIPESTATUS打印1退出状态为false
ilkkachu

支持PIPESTATUS和pipefiail,但|exho确实令人困惑
eck17年

0

Shell在执行管道之前先评估命令行。$?在管道之前执行的最后一条命令的值也是如此。无论echo本身开始之前或之后which无关。

现在,如果您的问题专门关于出口状态的传播,则可以研究如下默认行为:

% false | true
% echo $?
0

% true | false
% echo $?
1

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.