Rubyから外部プログラムを起動する方法まとめ
簡単なまとめ
Open3.capture3 Open3.capture2 Open3.capture2e | 普通に起動するとき |
Open3.popen3 Open3.popen2 Open3.popen2e | 外部プログラムにパイプでつなぎたいとき |
バッククオート | 書き捨てスクリプト用(出力がほしい場合) |
system | 書き捨てスクリプト用(出力がいらない場合) |
IO.popen IO.pipe | 使いません。 |
exec spawn fork など | 使いません。 |
systemu | 使いません。 |
Open3.capture2
Open3.capture2e
Open3.capture3
Rubyで外部プログラムを実行する、たいがいのケースはOpen3.capture3
でまかなえます。
外部コマンドを実行し、標準出力・標準エラーを文字列で取れ、終了コードも受け取れます。 標準入力に文字列を渡すこともできます。
require "open3"
out, err, status = Open3.capture3("echo a; sort >&2", :stdin_data=>"foo\nbar\nbaz\n")
p out #=> "a\n"
p err #=> "bar\nbaz\nfoo\n"
p status.exitstatus #=> 0
また、標準出力は欲しいがエラーはコンソールにそのまま出したい場合や、
標準出力と標準エラーをいっしょくたにして取得したい場合は、
それぞれPpen3.capture2
Open3.capture2e
という変種を使います。
Open3.popen2
Open3.popen2e
Open3.popen3
Open3.capture*
では、Rubyスクリプトは外部プログラムが終了するのを待って、それから次の処理に進みます。
しかし、外部プログラムと並列に実行させたい場合は、Open3.popen3
を使います。
Open3.popen3
からは標準入力・標準出力・標準エラーのファイルオブジェクトと、
外部プログラムの終了を待つためのスレッドが取れます。
require "open3"
# nroff を実行してその標準入力に man ページを送り込み処理させる。
# nroff プロセスの標準出力から処理結果を受け取る。
stdin, stdout, stderr, wait_thr = *Open3.popen3('nroff -man')
# こちらから書く
Thread.fork {
File.foreach('/usr/man/man1/ruby.1') do |line|
stdin.print line
end
stdin.close # または close_write
}
# こちらから読む
stdout.each do |line|
print line
end
p wait_thr.value.exitstatus # => 0
なお、stdin.close
などで標準入力を閉じずにstdout.each_line
などを呼ぶとハングアップする場合があるなど、
Open3.popen3
にはやや注意が必要です。
また Open3.popen2
Open3.popen2e
という変種もあります。
バッククオートと system
バッククオートはコマンドを実行して、その標準出力を文字列として取得します。
一方 system
は実行結果によって true
(成功) false
(失敗) nil
(実行できなかった) を返します。
標準出力はそのまま出ていきます。
puts `ruby -v` #=> ruby 2.0.0p353 (2013-11-22 revision 43784) [x86_64-linux]
system "ruby -v"
#(コンソールに)ruby 2.0.0p353 (2013-11-22 revision 43784) [x86_64-linux]
puts $?.exitstatus #=> 0
しかし、バッククオートとsystem
では標準エラー出力はとってこれません。
また、終了コードもグローバル変数$?
からとるなど、やや特殊です。
スクリプトでは遅かれ早かれ、エラー処理のために標準エラー出力や終了コードが欲しくなります。
であるならば、初めから Open3.*
を使った方が統一的になるでしょう。
もっとも、バッククオートとsystem
はrequire
が不要であり、
irbから呼び出したりエラー処理を気にしない書き捨てスクリプトで使う分には手軽です。
IO.popen
IO.pipe
exec
spawn
fork
など
使いません 。
これらは、過去との互換性をとったり、C言語のプログラムを移植するためにあります。
特別な理由が無い限りOpen3.*
を使いましょう。
systemu
systemu
はOpen3.*
と同じようなことをするgemです。
しかし、 Open3.*
は標準で入っている し、systemu
は一時ファイルを使っているため遅いようです。
特別な理由がない限り Open3.*
を使いましょう。
なお「特別な理由」としては、ブロックを渡して、外部プログラムの実行に時間がかかった時に強制終了させたりできます。
require 'systemu'
looper = %q( ruby -e" loop{ STDERR.puts Time.now.to_i; sleep 1 } " )
status, stdout, stderr =
systemu looper do |cid|
sleep 3
Process.kill 9, cid
end
共通の注意点
Rubyで外部プログラムを起動する方法はどれも、メタ文字を含む場合はシェル経由で実行します。
メタ文字: * ? {} [] <> () ~ & | \ $ ; ' ` " \n
そのため必要に応じてShellwords.escapeなどで エスケープが必要です。
むすび
Rubyは優れた言語です。
私もRubyを仕事で使っています(Cucumber/Capybara/Sauce Labs でテストをしています)
Enumerable#find_all
は大好きです。Hash#fetch
も大好きです。
メソッドチェーンで配列からハッシュを構築するとβエンドルフィンが出ます。
でも、どうしても心の底からRubyを愛せないのです。
それは、昔私がまだ少年だった頃、最新のオブジェクト指向言語Ruby(笑)を身につけようとして、 トップレベルでも、オープンクラスでもなく、外部プログラムを起動する方法がわからないせいで*1、習得を諦めた苦い体験があったからかもしれません。
この記事を、新たにRubyに手を染める若い人達に捧げます。