如何在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#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 #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