为什么使用grep -q命令会出现exit code 141?

79

有人能解释一下我为什么会从下面的代码中得到141的退出代码吗?

#!/usr/bin/bash

set -o pipefail

zfs list | grep tank
echo a ${PIPESTATUS[@]}

zfs list | grep -q tank
echo b ${PIPESTATUS[@]}

cat /etc/passwd | grep -q root
echo c ${PIPESTATUS[@]}

我理解

...
a 0 0
b 141 0
c 0 0

据我理解,退出码141表示失败,但是上面一行显示零,所以应该是成功,我认为。

4个回答

117
这是因为grep -q一旦找到匹配项就会立即以零状态退出。尽管zfs命令仍在写入管道,但没有读取器(因为grep已退出),所以它会收到来自内核的SIGPIPE信号并以141状态退出。
另一个常见的出现这种行为的地方是head。例如:
$ seq 1 10000 | head -1
1

$ echo ${PIPESTATUS[@]}
141 0

在这种情况下,head读取了第一行并终止,从而生成了一个SIGPIPE信号,导致seq141退出。 请参阅《Linux程序员指南》中的“臭名昭著的SIGPIPE信号”。

49
传统上,退出状态码N大于128表示程序是由信号N-128终止的。由于 SIGPIPE 是信号13,因此141-128=13表示您的程序是由SIGPIPE终止的。 - chepner
20
有没有办法我仍然可以使用set -o pipefailgrep -q,因为我想保留它们,由于我需要从SSH进行大量解析。 - Sandra Schlichting
4
这并不是惯例的问题,而是关于Shell如何处理因信号而退出的进程的问题。对于Bash来说,它会返回 128 + 信号编号,但其他Shell使用其他公式。请参阅这篇优秀的文章:http://unix.stackexchange.com/a/99134/9041。 - Flimm
3
请参考SIGPIPE处理的有效方法,了解在Bash中使用pipefail存在的问题以及替代方案。 - akhan
4
@SandraSchlichting: 我有同样的问题,非常烦人。一个想法:使用 grep 时不要使用 -q 选项,而是将输出重定向至 /dev/null 并合并标准输出和错误输出:1> /dev/null 2>&1。理论上,稍微慢一些,因为 grep 将处理整个输入,但实际上:它能运行。 - kevinarpe
显示剩余6条评论

6
我不太熟悉"zfs list"命令,但我猜测它在抱怨其标准输出被关闭了——与grep不同的是,grep -q会在找到匹配项后立即退出。

3

另一个选项是不使用管道,而是使用进程替代:

grep -q tank <(zfs list)

更新:我猜这是一样的,因为在括号内运行的进程也将接收sigpipe信号。


如果正在寻找一个简单的解决方案,我认为一个非常简单的解决方案是将生产者的输出分配给一个变量,并将该变量“echo”到grep。当“grep -q”关闭管道时,似乎“echo”不会出现错误。 - Marcus
1
@Marcus:值得注意的是,当使用中间变量时,生产者的输出不再是流式的。生产者的输出一次性全部存储在变量中,并且只有在生产者完成后,grep的处理才能开始。 - nishanthshanmugham
其实这是一个很好的答案:head -1 <(seq 1 10000),返回代码是0。 - undefined

0
你可以继续享用输出,例如:
command | { head -n1; cat >/dev/null; }

为什么不直接去掉“-q”和“cat”呢?用“grep whatever >/dev/null”就可以了。 - undefined
你说得对,这确实没有意义,但对于头部-n1来说是有效的。我会尝试编辑我的回答。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接