我完全失去理智了。
假设我想回显单词
"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
输出会改变什么吗?每次它总是返回完全相同的字体列表。有人可以解释一下这是怎么回事吗? 🫠
更重要的是:我该如何解决它?
提前致谢。
fc-list
一旦找到匹配项就会退出。当它退出时,它会破坏管道:
grep
尚未完成发送所有内容。由于管道信号损坏 (echo
),echo
异常终止,因此失败。您设置了SIGPIPE
,这样整个管道就被认为失败了。
这是pipefail
的缺点。当您遇到
pipefail
时,当且仅当 command | grep -q pattern
没有发生时,您通常希望失败。如果 pattern
失败了,那也没关系;如果它在失败之前产生了匹配,那么就满足了。POSIX shell 及其前身是围绕此类用例设计的; Bash 为需要检查所有管道元素是否成功的情况引入了 command
。如果第二个或后续管道元素中的任何一个突然退出,导致前一个管道破裂,它将无法工作。
pipefail
在
grep -q
完成写入管道末端之前退出(关闭其管道末端),因此 fc-list
在尝试写入时会收到 SIGPIPE 信号继续写入,然后出错退出。fc-list
不会发生这种情况的唯一原因是
echo
发送输出的速度更快,因此它会在 echo
有时间找到匹配项并退出之前完成写入。如果 grep
速度较慢,或者输出足够大(特别是,足够大以填充管道的缓冲区,因此 echo
无法立即写入所有内容),您也会在 echo
中看到此问题。这种类型的时序相关行为称为“竞争条件”,正确的解决方案不是尝试修复时序,而是编写不依赖于时序的代码。对于此类管道故障,有几种可能的解决方案:
不要使用echo
pipefail
替换为 grep -q somepattern
grep somepattern >/dev/null
相同的状态指示,但它总是读取整个输入(并打印匹配项,但重定向会丢弃它)。同样,您可以将 grep -q
替换为 head -n30
。强制管道中间命令的成功状态,例如将 sed -n "1,30 p"
替换为 ... | fc-list | ...
... | (fc-list || true) | ...
的 所有错误,而不仅仅是管道故障)。