88

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

当搅拌机进程仍在运行时,我似乎无法捕捉到搅拌机通常打印到外壳的进度消息,我尝试了几种方法。在搅拌机退出后,我似乎总是访问搅拌机的标准输出,而不是在它仍在运行时。

这是一个失败的尝试示例。它确实获取并打印了搅拌机输出的前 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}

编辑:

为了让它更清楚一点,调用 blender 的命令会在 shell 中返回一个输出流,指示进度(第 1-16 部分已完成等)。似乎在搅拌机退出之前,任何对“获取”输出的调用都会被阻止。问题是如何在搅拌机仍在运行时访问此输出,因为搅拌机将其输出打印到外壳。

4

6 回答 6

177

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

以下列方式使用 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
于 2009-07-22T02:55:04.343 回答
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
于 2009-07-20T17:37:51.653 回答
7

STDOUT.flush 或 STDOUT.sync = true

于 2009-07-20T17:36:32.857 回答
4

Blender 在结束程序之前可能不会打印换行符。相反,它正在打印回车符 (\r)。最简单的解决方案可能是搜索使用进度指示器打印换行符的魔术选项。

问题是IO#gets(和其他各种 IO 方法)使用换行符作为分隔符。他们将读取流,直到遇到“\n”字符(搅拌机不发送)。

尝试设置输入分隔符$/ = "\r"blender.gets("\r")改用。

顺便说一句,对于诸如此类的问题,您应该始终检查puts someobj.inspectp someobj(两者都做同样的事情)以查看字符串中的任何隐藏字符。

于 2009-07-20T20:21:52.790 回答
0

我不知道当时 ehsanul 是否回答了这个问题,还有Open3::pipeline_rw()可用的,但它确实让事情变得更简单。

我不了解 ehsanul 在 Blender 中的工作,所以我用tarand做了另一个例子xztar将输入文件添加到标准输出流,然后xzstdout其再次压缩到另一个标准输出。我们的工作是获取最后一个标准输出并将其写入我们的最终文件:

require 'open3'

if __FILE__ == $0
    cmd_tar = ['tar', '-cf', '-', '-T', '-']
    cmd_xz = ['xz', '-z', '-9e']
    list_of_files = [...]

    Open3.pipeline_rw(cmd_tar, cmd_xz) do |first_stdin, last_stdout, wait_threads|
        list_of_files.each { |f| first_stdin.puts f }
        first_stdin.close

        # Now start writing to target file
        open(target_file, 'wb') do |target_file_io|
            while (data = last_stdout.read(1024)) do
                target_file_io.write data
            end
        end # open
    end # pipeline_rw
end
于 2015-04-11T06:16:08.940 回答
0

老问题,但有类似的问题。

在没有真正改变我的 Ruby 代码的情况下,有用的一件事是用stdbuf包装我的管道,如下所示:

cmd = "stdbuf -oL -eL -i0  openssl s_client -connect #{xAPI_ADDRESS}:#{xAPI_PORT}"

@xSess = IO.popen(cmd.split " ", mode = "w+")  

在我的示例中,我想像 shell 一样与之交互的实际命令是openssl

-oL -eL 告诉它只缓冲 STDOUT 和 STDERR 到换行符。替换L0完全取消缓冲。

但是,这并不总是有效:有时目标进程会强制执行自己的流缓冲区类型,就像另一个答案指出的那样。

于 2020-02-12T07:29:00.447 回答