Rubyの外部プロセスのSTDOUTから継続的に読み取る


86

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

回答:


175

私は私のこの問題を解決することにある程度成功しました。同様の問題を抱えている人がこのページを見つけた場合に備えて、詳細と説明を以下に示します。しかし、詳細を気にしない場合は、ここに簡単な答えがあります

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ライブラリ。スループットが最大化されるように作成されています。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

7
これは素晴らしいことですが、stdinブロックとstdoutブロックのパラメーターを交換する必要があると思います。参照:ruby-doc.org/stdlib-1.9.3/libdoc/pty/rdoc/…–
Mike Conigliaro

1
ptyを閉じる方法は?pidを殺しますか?
Boris B.

素晴らしい答え。herokuのrakeデプロイスクリプトを改善するのを手伝ってくれました。「gitpush
Serge Seletskyy 2014年

1
私はもともとこの方法を使おうとしましたが、「pty」はWindowsでは使用できません。結局のところ、STDOUT.sync = true(以下mveermanの答えを)必要だことすべてです。これは、いくつかのサンプルコードを含む別のスレッドです
パックマン2016

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

私はこれを試しました。問題は同じです。その後、出力にアクセスできます。IO.popenは、最初の引数をコマンドとして実行することから始まり、それが終了するのを待つと思います。私の場合、出力はブレンダーがまだ処理している間にブレンダーによって与えられます。その後、ブロックが呼び出されますが、これは役に立ちません。
ehsanul 2009

これが私が試したことです。ブレンダーが実行された後、出力を返します。IO.popen( "blender -b mball.blend // renders / -F JPEG -x 1 -f 1"、 "w +")do |ブレンダー| Blender.each {| line | 行を置きます。出力+ =行;}終了
ehsanul 2009

3
あなたの場合、何が起こっているのかわかりません。上記のコードをyes終了しないコマンドラインアプリケーションでテストしたところ、機能しました。コードは次のとおりですIO.popen('yes') { |p| p.each { |f| puts f } }。ルビーではなく、ブレンダーに関係しているのではないかと思います。おそらく、blenderは常にSTDOUTをフラッシュするとは限りません。
Sinan Taifour 2009

さて、私はテストするために外部のルビープロセスでそれを試しました、そしてあなたは正しいです。ブレンダーの問題のようです。とにかく答えてくれてありがとう。
ehsanul 2009

ブレンダーが標準出力をフラッシュしなくても、結局のところ、ルビーを介して出力を取得する方法があることがわかりました。興味がある場合は、別の回答ですぐに詳しく説明します。
ehsanul 2009

6

STDOUT.flushまたはSTDOUT.sync = true


ええ、これは不完全な答えでした。あなたの答えはもっと良かった。
mveerman 2009

ラメじゃない!私のために働いた。
クレイブリッジ

より正確には:STDOUT.sync = true; system('<whatever-command>')
キャラム

4

Blenderはおそらく、プログラムを終了するまで改行を出力しません。代わりに、キャリッジリターン文字(\ r)を出力します。最も簡単な解決策は、進行状況インジケーターで改行を出力する魔法のオプションを検索することです。

問題は、IO#gets(および他のさまざまなIOメソッドが)区切り文字として改行を使用することです。「\ n」文字(ブレンダーが送信していない)に到達するまで、ストリームを読み取ります。

入力セパレータを設定する$/ = "\r"か、blender.gets("\r")代わりにを使用してみてください。

ところで、このような問題については、文字列内の非表示の文字を常に確認するputs someobj.inspectp someobj(どちらも同じことを行います)確認する必要があります。


1
与えられた出力を調べたところ、blenderが改行(\ n)を使用しているようですので、それは問題ではありませんでした。とにかくヒントをありがとう、私は次にこのようなものをデバッグするときにそれを覚えておきます。
ehsanul 2009

0

ehsanulが質問に答えたとき、Open3::pipeline_rw()まだ利用可能であったかどうかはわかりませんが、それは本当に物事を簡単にします。

私は別の例を作ったので、私は、ブレンダーでehsanulの仕事を理解していないtarxztar入力ファイルをstdoutストリームに追加し、それをxz取得し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

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、完全にバッファを解除します。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.