我最近重新安装了 RVM(按照http://rvm.io) 在全新安装 Ubuntu 12.10 后,我得到了一个 SSD 驱动器。
现在,当我输入:type rvm | head -1
我收到以下错误:
rvm is a function
-bash: type: write error: Broken pipe
但如果我立即重复该命令,那么我只会收到:
rvm is a function
看起来一切都正常?发生了什么事?我该怎么做才能解决这个问题?这种情况并不总是发生。它似乎更不定期发生。我试图找到某种模式,但还没有找到。
答案1
在这种情况下出现“管道破裂”的情况很少见,但很正常。
当你运行 时type rvm | head -1
,bashtype rvm
在一个进程中执行,head -1
在另一个进程中执行。1的标准输出type
连接到管道,将 的标准输入传输head
到“读取”端。两个进程同时运行。
该head -1
进程从 stdin 读取数据(通常以 8 kB 为单位),打印出一行(根据选项-1
),然后退出,导致管道的“读取”端关闭。由于该rvm
函数相当长(经过 bash 解析和重建后约为 11 kB),这意味着head
退出时type
仍有几 kB 的数据要写出。
此时,由于type
正在尝试写入另一端已关闭的管道,因此破碎的管道 – 它调用的 write() 函数将返回 EPIPE 错误,翻译为“管道损坏”。除了此错误之外,内核还会向 发送 SIGPIPE 信号type
,默认情况下会立即终止该进程。
(该信号在交互式 shell 中非常有用,因为大多数用户不希望第一个进程继续运行并尝试写入无处可去。同时,非交互式服务忽略 SIGPIPE - 长时间运行的守护进程因为这样一个简单的错误而死亡是不好的 - 所以他们发现错误代码非常有用。)
但是,信号传递并非 100% 立即,并且可能存在 write() 返回 EPIPE 的情况,而进程在收到信号之前会继续运行一小段时间。在这种情况下,type
有足够的时间注意到写入失败、转换错误代码,甚至在被 SIGPIPE 终止之前将错误消息打印到 stderr。(错误消息显示“-bash: type:”,因为这type
是 bash 本身的内置命令。)
这在多 CPU 系统上似乎更为常见,因为type
进程和内核的信号传递代码可以在不同的核心上同时运行。
可以通过修补type
内置函数(在 bash 的源代码中)来删除该消息,当它从 write() 函数接收到 EPIPE 时立即退出。
不过,这并不是什么值得担心的事情,并且它与您的rvm
安装没有任何关系。
答案2
你可以修理破损的管道以另一个过程为代价像这样插入tail -n +1
管道:
输入 rvm | tail -n +1 | head -1
指示打印输入的第一行和随后的所有内容。输出将与没有输出时完全相同,但程序足够聪明+1
,可以检查标准输出并干净地关闭管道。不再tail
tail -n +1
管道破裂。
答案3
让我们尝试一下yes
,无休止的打印过程是的……
以前,yes
当达到限制时,进程会被 SIGPIPE 杀死。
➜ set -o pipefail
➜ yes | head -n 1
y
➜ echo $?
141
我的解决方案
➜ yes | (head -n 1;dd status=none of=/dev/null)
y
# the process will still running and output to null
您可以yes
用您的程序替换。
答案4
我们可以用来awk
替换head
,而不是过早退出。(这应该适用于所有 awk 实现的变体):
type rvm | awk 'NR==1'
或者更一般地,对于前 N 行:
# First 10 lines:
type rvm | awk -v N=10 'NR<=N'
解释一下 awk 脚本:NR==1
是条件,当满足时,应采取行动。NR
是当前记录数的 awk 变量。 操作通常在 中的条件之后给出{...}
。 当省略操作时,默认打印整行{print}
。 所以awk 'NR==1'
意味着打印“记录数”为 1 的行,即第一行。
类似地,在 中awk -v N=10 'NR<=N'
,-v N=10
表示N=10
作为变量传递给以下 awk 脚本。 和'NR<=N'
表示打印“记录数”小于/等于 的行N
。
为什么这不会触发管道损坏错误?这是因为在上面的 awk 脚本中,它会处理每一行直到文件末尾,因此它不会过早关闭管道的读取端。
一些性能注意事项:
- 我们不会缓存这些行并将它们打印出来
END
,因此从内存角度来看,处理大量行是可以的 - 由于我们在这里不引用字段(例如,,,
$1
...$2
),所以不应该发生字段拆分,这进一步节省了潜在的长行和大量行的计算拆分。- (根据 POSIX 文档[1]:“在评估记录中字段的第一次引用之前,应根据正则表达式中的规则,使用读取记录时当前的 FS 值,将记录拆分为字段”)。
最后,奇怪的是,如果我们确实想模拟head
提前退出的行为(以及因此出现的管道破裂症状),我们可以添加exit
语句:
yes | awk -v N=10 'NR<=N; NR>N {exit}' # print 10 "yes"
[1]:POSIX awk 手册