2009-07-20 9 views
80

ruby​​スクリプトを使用してコマンドラインからblenderを実行したい場合は、blenderの出力を1行ずつ処理してGUIのプログレスバーを更新します。ミキサーが私が読む必要がある標準的な外部プロセスであることは本当に重要ではありません。Rubyの外部プロセスのSTDOUTから連続的に読み込みます

ブレンダープロセスがまだ実行されているときに、ブレンダーが普通に出力するメッセージをキャッチできないようで、いくつかの方法を試しました。私はいつも、ブレンダーの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} 

編集:

をコマンドは、ブレンダーを呼び出し、それは少し明確にするためにシェルで出力のストリームを返し、進捗状況を示します(パート1-16完了など)。ブレンダーが終了するまで、出力を「取得」する呼び出しはブロックされているようです。問題は、ブレンダーがシェルに出力されるので、ブレンダーの実行中にこの出力にアクセスする方法です。

答えて

161

私はこの問題を解決するのに成功しました。類似の問題を抱えている人がこのページを見つけた場合には、いくつかの説明とともに、詳細を示します。しかし、あなたが詳細を気にしないなら、はここに短い答え

を使用してください。(もちろん、独自のコマンドを使用して)次のように産卵は:

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 

そしてここであまりにも多くの詳細と、長い答えです:

本当の問題は、もしプロセスのdoesnのようです明示的にそのstdoutをフラッシュすると、stdoutに書き込まれたものは、IOが最小限になるように、実際に送信されるのではなくバッファされます(これはであると思われます)。スループットが最大になるようにより少ないIOを介して)。あなたがstdoutを定期的にフラッシュするようにプロセスを簡単に変更できるなら、それがあなたの解決策になります。私の場合は、それはミキサーだったので、ソースを変更するために自分自身のような完全な騒ぎのために少し威圧していました。

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

この動作は、出力をリアルタイムで収集する必要のある子プロセスとしてのルビプロセスでも発生する可能性があります。

そして、Rubyスクリプトはそれを呼び出すと、その出力を返す:

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

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

サブプロセスがリアルタイムでシェルに印刷できるのであれば、これをリアルタイムでRubyで取り込む方法が必要です。そこには。あなたは、私は信じて(1.8.6とにかく)ルビーコアに含まれているPTYモジュールを使用する必要があります。悲しいことは、それが文書化されていないということです。しかし、私は幸いにも使用のいくつかの例を見つけました。

まず、PTYとは何かを説明するために、それはpseudo terminalの略です。基本的に、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 
+0

+1 - ニースとよくできました! :) –

+6

これは良い答えです。私の一日を作った。 –

+7

これは素晴らしいですが、stdinとstdoutのブロックパラメータを交換する必要があります。参照:http://www.ruby-doc.org/stdlib-1.9.3/libdoc/pty/rdoc/PTY.html#method-c-spawn –

4

STDOUT.flushまたは STDOUT.sync =真

+0

はい、これは不自由な答えでした。あなたの答えはよかった。 – mveerman

+0

'STDOUT.sync = true'は私の魅力のように働いた –

12

使用IO.popenThisは良い例です。それは、プログラムを終了するまで、Blenderはおそらく改行を印刷しません

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 
+0

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

+0

私はこれを試してみました。 blenderが実行された後に出力を返します。 IO.popen( "blender -b mball.blend //レンダリング/ -F JPEG -x 1 -f 1"、 "w +")do | blender | blender.each {| line |行を置く。 output + = line;} end – ehsanul

+3

あなたのケースで何が起こっているのか分かりません。私は上記のコードを 'yes'とテストしました。コマンドラインアプリケーションは決してありません。コードは次のとおりです: 'IO.popen( 'yes'){| p | p.each {| f | f}} 'を入れます。私はそれがミキサーとは関係があり、ルビーではないと思っています。たぶんミキサーがSTDOUTをフラッシュするとは限りません。 –

4

あなたのコードのようなものになるでしょう。代わりに、キャリッジリターン文字(\ r)を印刷しています。最も簡単な解決策は、進行状況インジケータで改行を印刷する魔法のオプションを探していることでしょう。

IO#gets(およびその他のさまざまなIOメソッド)では、区切り文字として改行が使用されています。彼らは "\ n"文字(ブレンダーが送信していない)に当たるまでストリームを読み込みます。

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

ところで、これらの問題については、文字列内の隠された文字を確認するには、常にputs someobj.inspectまたはp someobj(どちらも同じことをします)をチェックする必要があります。

+1

私はちょうど与えられた出力を検査しました、そして、ブレンダーは改行(\ n)を使用しているようですので、それは問題ではありませんでした。とにかくチップのおかげで、私はこのような何かをデバッグしている次回は心に留めておきます。 – ehsanul

0

を私は時間ehsanulが質問に答えで、まだ利用Open3::pipeline_rw()があったかどうかわかりません、実際には簡単になります。

私はblanderのehsanulの仕事を理解していないので、tarxzという別の例を作りました。 tarは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 
関連する問題