88

コマンドラインからrubyスクリプトを介してblenderを実行したいのですが、その後、blenderからの出力を行ごとに処理して、GUIのプログレスバーを更新します。ブレンダーが外部プロセスであり、そのstdoutを読み取る必要があることはそれほど重要ではありません。

ブレンダープロセスがまだ実行されているときに、ブレンダーが通常シェルに出力する進行状況メッセージをキャッチできないようです。いくつかの方法を試しました。ブレンダーがまだ実行されている間ではなく、ブレンダーが終了した後、私は常にブレンダーの標準にアクセスしているようです。

失敗した試行の例を次に示します。ブレンダーの出力の最初の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を呼び出すコマンドは、進行状況を示す出力のストリームをシェルに返します(パート1-16が完了したなど)。ブレンダーが終了するまで、出力を「取得」するための呼び出しはブロックされているようです。問題は、blenderが出力をシェルに出力するときに、blenderの実行中にこの出力にアクセスする方法です。

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

そして、ここに長い答えがありますが、詳細が多すぎます:

本当の問題は、プロセスがstdoutを明示的にフラッシュしない場合、IOを最小限に抑えるために、プロセスが完了するまで、stdoutに書き込まれたものは実際に送信されるのではなく、バッファリングされることです(これは明らかに多くの実装の詳細です) Cライブラリ。IOの頻度を減らしてスループットを最大化するように作成されています)。stdoutを定期的にフラッシュするようにプロセスを簡単に変更できる場合は、それが解決策になります。私の場合、それはブレンダーだったので、私のような完全な初心者がソースを変更するのは少し怖かったです。

ただし、これらのプロセスをシェルから実行すると、stdoutがシェルにリアルタイムで表示され、stdoutはバッファリングされていないようです。私が信じている別のプロセスから呼び出された場合にのみバッファリングされますが、シェルが処理されている場合、stdoutはバッファリングされていない状態でリアルタイムに表示されます。

この動作は、出力をリアルタイムで収集する必要がある子プロセスとしてのrubyプロセスでも観察できます。次の行を使用して、random.rbというスクリプトを作成するだけです。

5.times { |i| sleep( 3*rand ); puts "#{i}" }

次に、それを呼び出してその出力を返すルビースクリプト:

IO.popen( "ruby random.rb") do |random|
  random.each { |line| puts line }
end

期待どおりにリアルタイムで結果が得られないことがわかりますが、その後は一度にすべてが得られます。自分でrandom.rbを実行しても、STDOUTはバッファリングされていますが、バッファリングされていません。STDOUT.flushこれは、random.rbのブロック内にステートメントを追加することで解決できます。ただし、ソースを変更できない場合は、これを回避する必要があります。プロセスの外部からフラッシュすることはできません。

サブプロセスがリアルタイムでシェルに出力できる場合は、Rubyを使用してこれをリアルタイムでキャプチャする方法も必要です。そこには。私が信じているrubyコア(とにかく1.8.6)に含まれているPTYモジュールを使用する必要があります。悲しいことに、それは文書化されていません。しかし、幸いなことにいくつかの使用例を見つけました。

まず、PTYとは何かを説明するために、疑似端末の略です。基本的に、rubyスクリプトは、コマンドをシェルに入力したばかりの実際のユーザーであるかのように、サブプロセスに表示されます。そのため、ユーザーがシェルを介してプロセスを開始した場合にのみ発生する変更された動作(この場合、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()んが、それは本当に物事を簡単にします。

私はBlenderでのehsanulの仕事を理解していないので、とで別の例を作りましtarxztar入力ファイルをstdoutストリームに追加し、それxzを取得しstdoutて、もう一度、別のstdoutに圧縮します。私たちの仕事は、最後のstdoutを取得し、それを最終ファイルに書き込むことです。

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+")  

私の例では、シェルであるかのように操作したい実際のコマンドはopensslです。

-oL -eL STDOUTとSTDERRを改行までのみバッファリングするように指示します。に置き換えL0、完全にバッファリングを解除します。

ただし、これは常に機能するとは限りません。別の回答で指摘されているように、ターゲットプロセスが独自のストリームバッファタイプを適用する場合があります。

于 2020-02-12T07:29:00.447 に答える