Bash:`fc-list | 令人惊讶的行为grep -q` 与 `pipefail`

问题描述 投票:0回答:2

我完全失去理智了。

假设我想回显单词

"Sans"
,然后用
grep
检查它是否包含子字符串
"Sans"
"CrazyStuff"

#!/bin/bash

echo Sans | grep -q Sans       && echo y || echo n
echo Sans | grep -q CrazyStuff && echo y || echo n

set -o pipefail

echo Sans | grep -q Sans       && echo y || echo n
echo Sans | grep -q CrazyStuff && echo y || echo n

正如预期的那样,

"Sans"
包含
"Sans"
,并且不包含
"CrazyStuff"
,因此无论
pipefail
设置如何,

  • 第一个
    grep Sans
    成功了
  • 第二个
    grep CrazyStuff
    失败了
  • 第三个
    grep Sans
    成功了
  • 第四个
    grep CrazyStuff
    失败了

输出为:

y
n
y
n

到目前为止一切顺利。


现在,让我们用

"Sans"
的输出替换常量字符串
fc-list
(它列出了已安装的字体;您可能还会有一些 Sans-Serif 字体,因此它应该包含
Sans
):

#!/bin/bash

captured="$(fc-list)"

echo "$captured" | grep -q Sans       && echo y || echo n
echo "$captured" | grep -q CrazyStuff && echo y || echo n

set -o pipefail

echo "$captured" | grep -q Sans       && echo y || echo n
echo "$captured" | grep -q CrazyStuff && echo y || echo n

由于

$captured
输出包含子字符串
Sans
,因此该程序的行为与第一个程序完全相同,输出再次为:

y
n
y
n

现在,大胆的特技:我们只需调用

echo
四次,而不是对
$captured
fc-list
输出进行操作:
fc-list

显然,它的行为方式必须与前两个示例完全相同,因此毫不奇怪,输出是

#!/bin/bash fc-list | grep -q Sans && echo y || echo n fc-list | grep -q CrazyStuff && echo y || echo n set -o pipefail fc-list | grep -q Sans && echo y || echo n fc-list | grep -q CrazyStuff && echo y || echo n

...等等,
什么

通过实际调用

y n n n

来替换

$captured
的常量
fc-list
输出会改变什么吗?每次它总是返回完全相同的字体列表。
有人可以解释一下这是怎么回事吗? 🫠

更重要的是:我该如何解决它?

提前致谢。

bash grep sigpipe
2个回答
3
投票
fc-list

一旦找到匹配项就会退出。当它退出时,它会破坏管道:

grep
尚未完成发送所有内容。由于管道信号损坏 (
echo
),
echo
异常终止,因此失败。
您设置了

SIGPIPE

,这样整个管道就被认为失败了。

这是

pipefail

的缺点。当您遇到

pipefail
时,当且仅当
command | grep -q pattern
没有发生时,您通常希望失败。如果
pattern
失败了,那也没关系;如果它在失败之前产生了匹配,那么就满足了。
POSIX shell 及其前身是围绕此类用例设计的; Bash 为需要检查所有管道元素是否成功的情况引入了 

command

。如果第二个或后续管道元素中的任何一个突然退出,导致前一个管道破裂,它将无法工作。

    


0
投票
pipefail

grep -q
完成写入管道末端之前退出(关闭其管道末端),因此
fc-list
在尝试写入时会收到 SIGPIPE 信号继续写入,然后出错退出。

fc-list

不会发生这种情况的唯一原因是

echo
发送输出的速度更快,因此它会在
echo
有时间找到匹配项并退出之前完成写入。如果
grep
速度较慢,或者输出足够大(特别是,足够大以填充管道的缓冲区,因此
echo
无法立即写入所有内容),您也会在
echo
中看到此问题。这种类型的时序相关行为称为“竞争条件”,正确的解决方案不是尝试修复时序,而是编写不依赖于时序的代码。
对于此类管道故障,有几种可能的解决方案:

不要使用

echo

。如果您不完全理解管道中的程序如何工作以及彼此之间以及管道之间的交互,那么它很容易导致此类问题。
  1. 避免使用无法读取完整输入的内容。在这种情况下,您可以将
    pipefail
    替换为
  2. grep -q somepattern
  3. ;这给出了与
    grep somepattern >/dev/null
    相同的状态指示,但它总是读取整个输入(并打印匹配项,但重定向会丢弃它)。同样,您可以将
    grep -q
    替换为
    head -n30
    强制管道中间命令的成功状态,例如将 
    sed -n "1,30 p"
    替换为
  4. ... | fc-list | ...
  5. (但请注意,这会抑制来自
    ... | (fc-list || true) | ...
    所有
    错误,而不仅仅是管道故障)。
© www.soinside.com 2019 - 2024. All rights reserved.