我在使用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#close
或IO#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
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
。