如何修复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"

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

因此,要解决此问题,只需在打印输出之前调用stdin.close即可。

您的代码挂起了,因为stdin仍然打开

如果使用popen3,则需要使用IO#closeIO#close_write关闭它。

如果使用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#关闭_写入

Ruby 1.9.2 IO#关闭_写入

Ruby 2.3.1 IO#close_write

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

尽管在你对sepp2k的回答的评论中,你表示你仍然经历了挂起。好吧,有些陷阱你可能忽略了。

stderr的缓冲区已满

如果您调用的程序向stderr打印的内容超过了匿名管道的缓冲区所能容纳的内容(当前Linux为64KiB),则该程序将被挂起。挂起的程序既不退出也不关闭stdout。因此,从其stdout读取将挂起。因此,如果你想做得好,你必须使用线程或IO.select,无阻塞、无缓冲的读取,以便并行或轮流从stdout和stderr读取,而不会陷入困境。

stdin的缓冲区已满

如果您试图向程序(cat)提供比"foobar"更多(多得多)的内容,则stdout的匿名管道缓冲区将满。操作系统将挂起cat。如果向stdin写入更多内容,则stdin的匿名管道缓冲区将满。那么您对stdin.write的呼叫将被卡住。这意味着:您需要并行或轮流向stdin写入、从stdout读取和从stderr读取。

结论

读一本好书(Richards-Stevens,"UNIX网络编程:进程间通信")并使用好的库函数。IPC(进程间通信)过于复杂,并且容易出现不确定的运行时行为。试图通过反复尝试来把事情做好太麻烦了。

使用Open3.capture3

相关内容

  • 没有找到相关文章

最新更新