在Ruby中连续读取外部进程的STDOUT

ehs*_*nul 84 ruby shell stdin stdout process

我想通过ruby脚本从命令行运行blender,然后逐行处理blender给出的输出以更新GUI中的进度条.混合器是我需要读取的stdout的外部进程并不重要.

当blender进程仍在运行时,我似乎无法捕获blender正常打印到shell的进度消息,并且我尝试了几种方法.搅拌机退出后,我似乎总是访问搅拌机的标准配置,而不是在它仍在运行时.

这是尝试失败的一个例子.它确实得到并打印了搅拌机输出的前25行,但只有在搅拌机过程退出后:

blender = nil
t = Thread.new do
  blender = open "| blender -b mball.blend -o //renders/ -F JPEG -x 1 -f 1"
end
puts "Blender is doing its job now..."
25.times { puts blender.gets}
Run Code Online (Sandbox Code Playgroud)

编辑:

为了使它更清晰一点,调用blender的命令会在shell中返回一个输出流,指示进度(第1-16部分已完成等).似乎任何"获取"输出的调用都会被阻止,直到混合器退出为止.问题是如何在blender仍在运行时访问此输出,因为blender将其输出打印到shell.

ehs*_*nul 173

我在解决这个问题上取得了一些成功.以下是详细信息和一些解释,以防有任何类似问题的人找到此页面.但如果你不关心细节,这里是简短的回答:

以下列方式使用PTY.spawn(当然使用您自己的命令):

require 'pty'
cmd = "blender -b mball.blend -o //renders/ -F JPEG -x 1 -f 1" 
begin
  PTY.spawn( cmd ) do |stdout, stdin, pid|
    begin
      # Do stuff with the output here. Just printing to show it works
      stdout.each { |line| print line }
    rescue Errno::EIO
      puts "Errno:EIO error, but this probably just means " +
            "that the process has finished giving output"
    end
  end
rescue PTY::ChildExited
  puts "The child process exited!"
end
Run Code Online (Sandbox Code Playgroud)

这里的长的答案,有太多的细节:

真正的问题似乎是如果一个进程没有显式刷新它的stdout,那么写入stdout的任何内容都会被缓冲而不是实际发送,直到进程完成,以便最小化IO(这显然是许多实现细节) C库,通过较少的IO来实现吞吐量最大化).如果您可以轻松修改流程以便定期刷新stdout,那么这将是您的解决方案.在我的情况下,这是一个搅拌器,所以有点像我自己修改源的完整菜鸟一样令人生畏.

但是当你从shell运行这些进程时,它们会实时向shell显示stdout,而stdout似乎没有被缓冲.它只是在从我认为的另一个进程调用时才被缓冲,但是如果正在处理一个shell,则会实时看到stdout,无缓冲.

甚至可以使用ruby进程观察到这种行为,因为子进程的输出必须实时收集.只需使用以下行创建一个脚本random.rb:

5.times { |i| sleep( 3*rand ); puts "#{i}" }
Run Code Online (Sandbox Code Playgroud)

然后是一个ruby脚本来调用它并返回它的输出:

IO.popen( "ruby random.rb") do |random|
  random.each { |line| puts line }
end
Run Code Online (Sandbox Code Playgroud)

你会发现你并没有像你期望的那样实时得到结果,但事后才会得到结果.STDOUT正在被缓冲,即使你自己运行random.rb,它也不是缓冲的.这可以通过STDOUT.flush在random.rb中的块内添加一个语句来解决.但是,如果你不能改变来源,你必须解决这个问题.您无法从流程外部清除它.

如果子进程可以实时打印到shell,那么必须有一种方法可以使用Ruby实时捕获它.而且有.您必须使用包含在ruby核心中的PTY模块我相信(1.8.6反正).可悲的是,它没有记录.但我幸运地找到了一些使用的例子.

首先,为了解释什么是PTY,它代表伪终端.基本上,它允许ruby脚本将自己呈现给子进程,就好像它是一个刚刚将命令输入shell的真实用户.因此,只有当用户通过shell启动进程(例如STDOUT未被缓冲,在这种情况下)时才会发生任何更改的行为.隐瞒另一个进程已启动此进程的事实允许您实时收集STDOUT,因为它没有被缓冲.

要使用random.rb脚本作为子项,请尝试以下代码:

require 'pty'
begin
  PTY.spawn( "ruby random.rb" ) do |stdout, stdin, pid|
    begin
      stdout.each { |line| print line }
    rescue Errno::EIO
    end
  end
rescue PTY::ChildExited
  puts "The child process exited!"
end
Run Code Online (Sandbox Code Playgroud)

  • 这很好,但我相信应该交换stdin和stdout块参数.请参阅:http://www.ruby-doc.org/stdlib-1.9.3/libdoc/pty/rdoc/PTY.html#method-c-spawn (7认同)

Sin*_*our 12

IO.popen.是一个很好的例子.

你的代码会变成这样的:

blender = nil
t = Thread.new do
  IO.popen("blender -b mball.blend -o //renders/ -F JPEG -x 1 -f 1") do |blender|
    blender.each do |line|
      puts line
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

  • 我不确定你的情况会发生什么.我使用`yes`测试了上面的代码,这是一个永远不会_ends_的命令行应用程序,并且它有效.代码如下:`IO.popen('yes'){| p | p.each {| f | 把f}}放进去.我怀疑它与搅拌机有关,而不是红宝石.可能搅拌机并不总是冲洗它的STDOUT. (3认同)

mve*_*man 5

STDOUT.flush或STDOUT.sync = true