Ruby STDIN,阻塞与不阻塞

Ruby STDIN, blocking vs not blocking

我正在尝试查找有关如何在 Ruby 中处理 STDIN 的文档。

我试验过这个简单的脚本:

# test.rb

loop do
  puts "stdin: #{$stdin.gets}"
  sleep 2
end

我 运行 来自 bash(在 OS X 上):

$ ruby test.rb

正如我所料,对 $stdin.gets 的调用是阻塞的,循环等待下一个输入。 2 秒的休眠时间甚至允许我一次输入更多行,并且循环正确地按顺序打印它们,然后在清除 STDIN 时再次停止:

$ ruby test.rb
a
stdin: a
b
stdin: b
c
d
e
stdin: c
stdin: d
stdin: e

到目前为止,一切都很好。我期待着这一点。

然后,我用管道做了个测试:

$ mkfifo my_pipe
$ ruby test.rb < my_pipe

并且,在另一个 shell 中:

$ echo "Hello" > my_pipe

这一次,它的表现有点不同。
起初它确实等待,阻塞了循环。但是,在第一个输入通过管道后,它一直循环并打印空字符串:

$ ruby test.rb
stdin: Hello
stdin:
stdin:
stdin: Other input
stdin:

所以我的问题是:为什么不同?它是否将管道视为空文件?这在哪里记录? docs 没有说任何有关阻塞行为的信息,但他们确实是这样说的:

Returns nil if called at end of file.

这是一个开始。

所以简短的回答是肯定的,您正在从管道中获取 EOF。因为 echo 的工作方式是打开管道进行写入,写入,然后关闭(即发送 EOF)。然后对 echo 的新调用将重新打开它,读取它,然后关闭。

如果您改为使用一个在 3 秒睡眠后打印文件行的程序,您会看到您的应用程序将执行阻塞等待,直到该程序退出(此时永无止境的 EOF 将 return).

# slow_write.rb
ARGF.each do |line|
  puts line
  STDOUT.flush
  sleep 3
end

我应该注意,此行为并非特定于 Ruby。 C stdlio 库具有完全相同的行为,并且由于大多数语言使用 C 基元作为其基础,因此它们也具有相同的行为。