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

maa*_*sha 14 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"
Run Code Online (Sandbox Code Playgroud)

sep*_*p2k 14

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

所以要解决这个问题,只需stdin.close在打印输出之前调用即可.

  • 只有当`stdin.puts`的参数很短时,此修复才有效.如果向与变量`stdin`关联的管道写入更多,更多的数据,`cat`将写入stdout的管道缓冲区,直到缓冲区已满.此时,操作系统将暂停"猫".如果在这种情况下向`stdin`写入更多数据,则填充stdin管道的缓冲区.当这个管道已满时,调用`stdin.puts`块.这意味着:您的程序挂起.因此,如果您有大量数据,则必须将写入交错到`stdin`并从`stdout`(和`stderr`)读取.为什么不使用`Open3.capture*`? (2认同)

Til*_*ilo 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
Run Code Online (Sandbox Code Playgroud)

也可以看看:

Ruby 1.8.7 IO #close_write

Ruby 1.9.2 IO#close_write

Ruby 2.3.1 IO #close_write


hag*_*llo 6

Tilo和sepp2k的答案是对的:如果你关闭stdin,你的简单测试就会结束.问题解决了.

虽然在您对sepp2k答案的评论中,您表明您仍然遇到挂起.好吧,你可能忽略了一些陷阱.

坚持stderr的完整缓冲区

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

卡在stdin的完整缓冲区上

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

Conlusion

阅读一本好书(Richards Stevens,"UNIX网络编程:进程间通信")并使用好的库函数.IPC(进程间通信)过于复杂,容易出现不确定的运行时行为.试图通过尝试和错误来纠正它是太麻烦了.

使用Open3.capture3.