如何在Ruby中修复挂起的popen3?
我使用popen3得到了意想不到的行为,我想用它来运行像ala cmd 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 #close_write
Ruby 1.9.2 IO#close_write
Ruby 2.3.1 IO #close_write
Tilo和sepp2k的答案是对的:如果你关闭stdin
,你的简单测试就会结束。 问题解决了。
虽然在您对sepp2k答案的评论中 ,您表明您仍然遇到挂起。 好吧,你可能忽略了一些陷阱。
坚持stderr的完整缓冲区
如果你调用一个程序打印更多的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
。