15

我在使用 popen3 时遇到了意外的行为,我想用它来运行类似 tool ala 的命令cmd < file1 > file2。下面的示例挂起,因此stdout done永远无法达到。使用其他工具cat可能会导致悬挂,因此stdin done永远无法达到。我怀疑,我正在遭受缓冲,但我该如何解决这个问题?

#!/usr/bin/env ruby

require 'open3'

Open3.popen3("cat") do |stdin, stdout, stderr, wait_thr|
  stdin.puts "foobar"

  puts "stdin done"

  stdout.each_line { |line| puts line }

  puts "stdout done"

  puts wait_thr.value
end

puts "all done"
4

3 回答 3

15

stdout.each_line正在等待进一步的输出,cat因为cat的输出流仍处于打开状态。它仍然处于打开状态,因为cat它仍在等待用户输入,因为它的输入流尚未关闭(您会注意到,当您cat在终端中打开并输入 时foobar,它仍将运行并等待输入,直到您按下^d关闭流)。

所以要解决这个问题,只需stdin.close在打印输出之前调用。

于 2012-01-21T09:51:15.190 回答
7

你的代码挂了,因为stdin它仍然是开放的!

如果您使用,则需要使用IO#close或关闭它。IO#close_writepopen3

如果你使用popen那么你需要使用IO#close_write,因为它只使用一个文件描述符。

 #!/usr/bin/env ruby
 require 'open3'

 Open3.popen3("cat") do |stdin, stdout, stderr, wait_thr|
   stdin.puts "foobar"

   stdin.close   # close stdin like this!  or with stdin.close_write

   stdout.each_line { |line| puts line }

   puts wait_thr.value
 end

也可以看看:

Ruby 1.8.7 IO#close_write

Ruby 1.9.2 IO#close_write

Ruby 2.3.1 IO#close_write

于 2013-05-15T15:00:20.590 回答
7

Tilo 和 sepp2k 的答案是正确的:如果您 close stdin,您的简单测试将结束。问题解决了。

尽管在您对 sepp2k 答案的评论中,您表示您仍然遇到挂起。嗯,有一些你可能忽略的陷阱。

卡在标准错误的完整缓冲区

如果您调用的程序向 stderr 打印的内容超过了匿名管道的缓冲区可以容纳的内容(当前 Linux 为 64KiB),则该程序将被挂起。挂起的程序既不退出也不关闭标准输出。因此,从其标准输出读取将挂起。因此,如果您想正确执行此操作,则必须使用线程或IO.select非阻塞、无缓冲读取,以便并行或轮流从 stdout 和 stderr 读取而不会卡住。

卡在标准输入的完整缓冲区

如果您尝试向程序 ( ) 提供比“foobar”更多(更多)cat的内容,则 stdout 的匿名管道的缓冲区将被填满。操作系统将暂停cat。如果您向 stdin 写入更多内容,则 stdin 的匿名管道的缓冲区将变满。然后你的电话stdin.write会卡住。这意味着:您需要并行或轮流写入标准输入、从标准输出读取和从标准错误读取。

结论

阅读一本好书(Richards Stevens,“UNIX 网络编程:进程间通信”)并使用好的库函数。IPC(进程间通信)太复杂了,容易出现不确定的运行时行为。尝试通过尝试和错误来正确解决问题太麻烦了。

使用Open3.capture3.

于 2014-11-01T23:09:30.310 回答