我在解决我的这个问题方面取得了一些成功。以下是详细信息和一些解释,以防遇到类似问题的人找到此页面。但如果你不关心细节,这里是简短的回答:
以下列方式使用 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
这是一个很长的答案,有太多的细节:
真正的问题似乎是,如果一个进程没有显式刷新其标准输出,那么写入标准输出的任何内容都会被缓冲而不是实际发送,直到进程完成,以最小化 IO(这显然是许多人的实现细节C 库,以便通过较少频率的 IO 最大化吞吐量)。如果您可以轻松地修改该过程以使其定期刷新标准输出,那么这将是您的解决方案。就我而言,它是搅拌机,所以对于像我这样的完整菜鸟来说修改源有点吓人。
但是当您从 shell 运行这些进程时,它们会实时向 shell 显示标准输出,并且标准输出似乎没有被缓冲。我相信它仅在从另一个进程调用时才被缓冲,但如果正在处理外壳,则标准输出会实时看到,没有缓冲。
这种行为甚至可以通过 ruby 进程作为必须实时收集其输出的子进程来观察。只需使用以下行创建一个脚本 random.rb:
5.times { |i| sleep( 3*rand ); puts "#{i}" }
然后是一个 ruby 脚本来调用它并返回它的输出:
IO.popen( "ruby random.rb") do |random|
random.each { |line| puts line }
end
您会发现,您并没有像预期的那样实时获得结果,而是在之后立即获得结果。STDOUT 正在被缓冲,即使您自己运行 random.rb,它也不会被缓冲。这可以通过STDOUT.flush
在 random.rb 的块内添加一条语句来解决。但是,如果您无法更改源,则必须解决此问题。您不能从进程外部刷新它。
如果子进程可以实时打印到 shell,那么必须有一种方法可以用 Ruby 实时捕获这一点。有。您必须使用 PTY 模块,我相信它包含在 ruby 核心中(无论如何都是 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