在尝试运行ruby程序并将输出通过管道传递到另一个程序时,如下所示:
ruby hello.rb | whoami
首先按预期执行命令whoami
,但是此后,hello.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)
只有在STDOUT.sync
设置为true
时才会发生
STDOUT.sync = true
STDOUT.print "Hello!"
[[并且在通过管道传输到另一个程序时,STDOUT.flush
之后的STDOUT.puts
会引发类似的错误]
此崩溃的原因是什么?
答案 0 :(得分:1)
首先,可以找到说明here。
无论如何,这是我的想法...
当这样使用管道时:
a | b
a和b均执行concurrently。 b等待来自a的标准输入。
谈到Errno::EPIPE
,Linux man page of write说:
EPIPE fd连接到读数端为 关闭。当发生这种情况时,写作过程也将 接收SIGPIPE信号。 (因此,写入返回值为 仅在程序捕获,阻止或忽略此内容时可见 信号。)
谈论问题中的问题:
运行程序whoami
时,它退出并且不再接受ruby程序hello.rb
发送的标准输入-导致管道损坏。
在这里,我编写了2个红宝石程序,分别名为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)
#!/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程序进行测试。水晶是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)
并退出。同样,两个Crystal程序可能要花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
,因为这两个程序之间的管道中断了(如果我在这里迷路了,请纠正我)。