首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >在Ruby中连续读取外部进程的STDOUT

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

Stack Overflow用户
提问于 2009-07-20 17:33:05
回答 6查看 34.5K关注 0票数 88

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

当blender进程仍在运行时,我似乎无法捕捉blender通常打印到shell的进度消息,我已经尝试了几种方法。我似乎总是在blender退出后访问blender的stdout,而不是在它还在运行的时候。

这里有一个失败的尝试的例子。它确实获取并打印了blender输出的前25行,但仅在blender进程退出之后:

代码语言:javascript
复制
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}

编辑:

为了更清楚一些,调用blender的命令在shell中返回一个输出流,指示进度(第1-16部分完成等)。似乎对“get”输出的任何调用都会被阻塞,直到blender退出。问题是如何在blender仍在运行时访问此输出,因为blender将其输出打印到shell。

EN

回答 6

Stack Overflow用户

回答已采纳

发布于 2009-07-22 02:55:04

我在解决我的这个问题上取得了一些成功。下面是一些细节,以及一些解释,以防任何有类似问题的人发现这个页面。但是如果你不关心细节,这里有一个简短的答案

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

代码语言:javascript
复制
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

下面是长长的答案,包含了太多的细节:

真正的问题似乎是,如果一个进程没有显式地刷新它的stdout,那么写到stdout的任何东西都会被缓冲,而不是实际发送,直到进程完成,以便最小化IO (这显然是许多C库的实现细节,通过降低IO频率来最大化吞吐量)。如果您可以轻松地修改该进程,使其定期刷新stdout,那么这将是您的解决方案。在我的例子中,它是blender,所以对于像我这样的新手来说,修改源代码有点吓人。

但是,当您从shell运行这些进程时,它们会向shell实时显示stdout,并且stdout似乎没有缓冲。我相信只有当从另一个进程调用它时,它才会被缓冲,但如果正在处理一个shell,那么stdout是实时可见的,没有缓冲。

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

代码语言:javascript
复制
5.times { |i| sleep( 3*rand ); puts "#{i}" }

然后使用ruby脚本调用它并返回其输出:

代码语言:javascript
复制
IO.popen( "ruby random.rb") do |random|
  random.each { |line| puts line }
end

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

如果子进程可以实时打印到shell,那么一定也有办法使用Ruby实时捕获这一点。事实就是如此。你必须使用PTY模块,我相信它包含在ruby核心中(不管怎样是1.8.6)。可悲的是,它没有记录在案。但幸运的是,我找到了一些使用的例子。

首先,为了解释什么是PTY,它代表pseudo terminal。基本上,它允许ruby脚本将其自身呈现给子进程,就像它是一个真正的用户刚刚在shell中输入了命令一样。因此,只有当用户通过shell (比如本例中没有缓冲的STDOUT )启动进程时,才会发生任何改变的行为。隐藏另一个进程已经启动这个进程的事实允许您实时收集STDOUT,因为它没有被缓冲。

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

代码语言:javascript
复制
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
票数 177
EN

Stack Overflow用户

发布于 2009-07-20 17:37:52

使用IO.popenThis就是一个很好的例子。

您的代码将变成如下所示:

代码语言:javascript
复制
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
票数 12
EN

Stack Overflow用户

发布于 2009-07-20 17:36:33

STDOUT.flush或STDOUT.sync = true

票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/1154846

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档