[解決済み] Rubyで外部プロセスのSTDOUTから継続的に読み込む
質問
blenderをコマンドラインからrubyスクリプトで実行し、blenderの出力を一行ずつ処理してGUI上のプログレスバーを更新したいのですが、blenderが外部プロセスであることはあまり重要ではありません。blenderが、私が読む必要のある標準出力である外部プロセスであることは、本当に重要ではありません。
blenderプロセスがまだ実行されているとき、blenderが通常シェルに出力するプログレスメッセージをキャッチすることができないようで、いくつかの方法を試してみました。私はいつもblenderの標準出力にアクセスするようです。 の後に blenderが終了した後、まだ実行中ではなく、blenderの標準出力にアクセスするようです。
これは失敗した試みの例です。これはblenderの出力の最初の25行を取得してプリントしていますが、blenderのプロセスが終了した後だけです。
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が完了したなど)。それは、"get" への呼び出しは、blender が終了するまでブロックされるようです。問題は、blenderがシェルに出力をプリントするように、blenderがまだ実行されている間、この出力にアクセスする方法です。
どのように解決するのですか?
私は自分のこの問題を解決するためにいくつかの成功を収めました。同じような問題を抱えている人がこのページを見つけられるように、ここに詳細といくつかの説明を掲載します。しかし、もしあなたが詳細を気にしないのであれば。 これが簡単な答えです。 :
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 の頻度を減らすことでスループットを最大化するように作られています)。もし、stdout を定期的にフラッシュするようにプロセスを簡単に変更できるなら、それがあなたの解決策になるでしょう。私の場合、それはblenderだったので、私のような全くの素人がソースを修正するのは少し気が引けました。
しかし、これらのプロセスをシェルから実行すると、それらはリアルタイムでシェルに標準出力を表示し、標準出力はバッファリングされていないようです。他のプロセスから呼び出されたときのみバッファリングされると思いますが、シェルが処理されている場合、stdout はバッファリングされずにリアルタイムで表示されます。
この動作は、出力をリアルタイムで収集する必要がある子プロセスとして、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
期待したようにリアルタイムに結果が出るわけではなく、その後一気に結果が出ることがわかると思います。random.rbを自分で実行するとバッファリングされないのに、STDOUTはバッファリングされているのです。これは
STDOUT.flush
ステートメントを追加することで解決できます。しかし、ソースを変更できない場合は、これを回避する必要があります。プロセスの外からフラッシュすることはできません。
サブプロセスがリアルタイムにシェルにプリントできるのであれば、これをRubyでもリアルタイムにキャプチャする方法があるはずです。そして、それはあります。Rubyのコアに含まれているPTYモジュール(1.8.6)を使わなければなりません。悲しいのは、それがドキュメント化されていないことです。しかし、私は幸いにもいくつかの使用例を見つけました。
まず、PTYが何であるかを説明します。 疑似端末 . 基本的に、これは ruby スクリプトがサブプロセスに対して、あたかもシェルにコマンドを入力した本当のユーザであるかのように振舞うことを可能にします。そのため、ユーザーがシェルを通じてプロセスを開始した場合にのみ発生する変更された動作(この場合、STDOUTがバッファリングされないなど)が発生します。別のプロセスがこのプロセスを開始したことを隠すことで、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
関連
-
[解決済み] Rubyからシェルコマンドを呼び出す方法
-
[解決済み] シェルスクリプトからエコーを使わずにパスワードを取得する方法
-
[解決済み】Pythonでstdoutをファイルにリダイレクトする?
-
[解決済み】PHPでの非同期シェル実行
-
[解決済み] rvm use 2.0.0 --default を実行すると、"Warning! PATH is not properly set up" と表示される。
-
[解決済み] ハッシュの配列をハッシュの値でソートするにはどうしたらいいですか?
-
[解決済み] Ruby: HTTP でファイルを multipart/form-data で投稿するには?
-
[解決済み] rspecでテストグループを無効にする?
-
[解決済み] Ruby の正規表現で最初にマッチしたものを返す
-
[解決済み] クラスのインスタンスメソッドの一覧を取得する
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] この blob があるのはどのコミットですか?
-
[解決済み] Rubyのプライベートモジュールメソッド
-
[解決済み] RubyのREPLを開くコマンドは何ですか?
-
[解決済み] 文字列内の複数の空白と改行を削除する
-
[解決済み] 変数名を使ったRubyの正規表現
-
[解決済み] Ruby 配列を関数の引数に変換する
-
[解決済み] Rubyで親のクラス名を取得するには?
-
[解決済み] クラスのインスタンスメソッドの一覧を取得する
-
[解決済み] Mavericks と Xcode 5.1 で Ruby Gem install Json が失敗する - unknown argument: '-multiply_definedsuppress'.
-
[解決済み] インスタンス変数の値をその名前で取得する