如何在Ruby中修复挂起的popen3?

时间:2012-01-21 09:46:03

标签: ruby popen3

我使用popen3获得意外行为,我想用它来运行像工具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"

3 个答案:

答案 0 :(得分:13)

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

因此,要解决此问题,只需在打印输出之前调用^d

答案 1 :(得分: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

答案 2 :(得分:5)

Tilo和sepp2k的回答是正确的:如果你关闭stdin,你的简单测试就会结束。问题解决了。

虽然在对answer of sepp2k的评论中,您表示您仍然遇到挂起。 好吧,你可能忽略了一些陷阱。

卡在stderr的完整缓冲区

如果您调用的程序打印的内容比匿名管道的缓冲区可以容纳的更多(当前Linux的64KiB),程序将被暂停。暂停的程序既不退出也不关闭stdout。因此,从它的标准读取将会挂起。因此,如果你想做得对,你必须使用线程或IO.select,非阻塞,无缓冲读取,以便并行或轮流读取stdout和stderr而不会卡住。

卡在stdin的完整缓冲区

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

Conlusion

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

使用Open3.capture3