简介
首先可以找到解释here。
无论如何,这是我的想法......
当这样使用管道时:
a | b
a 和 b 都执行concurrently。 b 等待来自 a 的标准输入。
说到Errno::EPIPE,Linux man page of write 说:
EPIPE fd 连接到读取端为
关闭。当这种情况发生时,写作过程也将
接收 SIGPIPE 信号。 (因此,写返回值为
只有当程序捕获、阻塞或忽略它时才能看到
信号。)
谈问题中的问题:
当程序 whoami 运行时,它退出并且不再接受 ruby 程序 hello.rb 发送的标准输入 - 导致管道损坏。
我在这里编写了 2 个 ruby 程序,分别命名为 p.rb 和 q.rb 来测试:
#!/usr/bin/env ruby
print ?* * 100_000
#!/usr/bin/ruby
exit! 0
跑步:
bash[~] $ ruby p.rb | ruby q.rb
Traceback (most recent call last):
2: from p.rb:2:in `<main>'
1: from p.rb:2:in `print'
p.rb:2:in `write': Broken pipe @ io_write - <STDOUT> (Errno::EPIPE)
让我们稍微修改一下q.rb的代码,让它接受输入:
#!/usr/bin/ruby -w
STDIN.gets
跑步:
bash[~] $ ruby p.rb | ruby q.rb
是的,它实际上什么也没显示。原因是 q.rb 现在等待标准输入。 显然,这里最重要的是等待。现在,即使使用STDOUT.sync 或STDOUT.flush 传送到此 q.rb,p.rb 也不会崩溃。
另一个例子:
STDOUT.sync = true
loop until print("\e[2K<<<#{Time.now.strftime('%H:%M:%S:%2N')}>>>\r")
[警告:没有睡眠的循环可能会增加你的 CPU 使用率]
sleep 3
跑步:
bash[~] $ time ruby p.rb | q.rb
Traceback (most recent call last):
2: from p.rb:2:in `<main>'
1: from p.rb:2:in `print'
p.rb:2:in `write': Broken pipe @ io_write - <STDOUT> (Errno::EPIPE)
real 0m3.186s
user 0m0.282s
sys 0m0.083s
您会看到程序在 3 秒后崩溃。如果 q.rb 有sleep 5,它将在 5.1 秒后崩溃。同样,q.rb 中的sleep 0 会在 0.1 秒后崩溃 p.rb。我猜额外的 0.1 秒取决于系统,因为我的系统需要 0.1 秒来加载 ruby 解释器。
我编写了 p.cr 和 q.cr Crystal 程序进行测试。 Crystal 是 compiled,加载时间不长,需要 0.1 秒。
水晶计划:
STDOUT.sync = true
loop do print("\e[2KHi!\r") end rescue exit
sleep 3
我编译它们,然后运行:
bash[~] $ time ./p | ./q
real 0m3.013s
user 0m0.007s
sys 0m0.019s
二进制文件 ./p 在非常接近 3 秒的时间内处理 Unhandled exception: Error writing file: Broken pipe (Errno) 并退出。同样,两个水晶程序执行可能需要 0.01 秒,并且内核可能也需要一些时间来运行进程。
还要注意STDERR#print、STDERR#puts、STDERR#putc、STDERR#printf、STDERR#write、STDERR#syswrite 不会引发 Errno::EPIPE,即使输出是同步的。
结论
管道是arcane。将STDOUT#sync 设置为true 或使用STDOUT#flush 会将所有缓冲数据刷新到底层操作系统。
在运行hello.rb | whoami时,没有同步,我可以写入8191字节的数据,并且程序hello.rb没有崩溃。但是使用同步,通过管道写入 1 个字节会崩溃 hello.rb。
所以当 hello.rb 将标准输出与管道程序 whoami 同步时,whoami 不会等待 hello.rb; hello.rb 引发 Errno::EPIPE,因为这两个程序之间的管道已损坏(如果我在这里迷路了,请纠正我)。