コマンドの標準出力/標準エラー/終了ステータスを保持したい
ruby スクリプトからコマンドを実行したいのだがコマンドの標準出力、標準エラー、終了ステータスはその都度保持しておきたい。「system/IO.popen/Open3.popen3 を使えばなんとかなるのでは…」と思っていたのだが、実現できなかったので自分で新たに Command クラスを作成してみた。内部で Process.fork を使って子プロセスを生成し、IO.pipe を使って子から親に標準出力/標準エラーを渡した後に、Process.waitpid2 で終了ステータスを取得している。データは do メソッドを呼ぶ度に @info に格納し、履歴として @history にどんどん @info を追加していく。
■要求
- - コマンドを実行し、標準出力/標準エラー/終了ステータスをデータとして格納すること。
- - 上記のデータをいつでも参照できること。
■実装(独自 Command クラス定義)
class Command attr_reader :info attr_reader :history def initialize () @info = {} @history = [] end def do(*elem) line = elem.join(" ") time = Time.now rp1, wp1 = IO.pipe rp2, wp2 = IO.pipe Process.fork do rp1.close rp2.close STDOUT.reopen(wp1) STDERR.reopen(wp2) exec line wp1.close wp2.close end wp1.close wp2.close stdout = rp1.read stderr = rp2.read rp1.close rp2.close pid, status = Process.waitpid2 @info = { :line => line, :pid => pid, :status => status, :exitstatus => status.exitstatus, :time => time, :stdout => stdout.chomp, :stderr => stderr.chomp } @history.push(@info) end end
■使い方
cmd = Command.new() cmd.do("ls -l", "/etc") cmd.do("date") cmd.do("ls -l noExistFile") print "=== command history ===\n" cmd.history.each do |h| print "time => ", h[:time], "\n" print "line => ", h[:line], "\n" print "exitstatus => ", h[:exitstatus], "\n" print "status => ", h[:status], "\n" print "stdout => ", h[:stdout], "\n" print "stderr => ", h[:stderr], "\n" print "-----\n\n" end
■出力結果
=== command history === time => Tue May 26 12:42:09 +0900 2009 line => ls -l /etc exitstatus => 0 status => 0 stdout => 合計 1456 drwxr-xr-x 4 root root 4096 2009-04-20 23:01 ConsoleKit drwxr-xr-x 4 root root 4096 2009-04-20 23:09 NetworkManager drwxr-xr-x 2 root root 4096 2009-04-20 23:09 PolicyKit drwxr-xr-x 9 root root 4096 2009-05-21 13:20 X11 drwxr-xr-x 8 root root 4096 2009-05-21 15:37 acpi (...snip...) stderr =>
-
-
-
- -
-
-
-
-
-
- -
-
-
-
-
-
- -
-
-
■不採用案
- - system だとコマンドの標準出力/標準エラーが端末に出てしまう(STDOUT/ERR.reopen で system をはさめば OK)。
- - IO.popen だと標準エラーが端末に出てしまう。
- - Open3.popen3 だと終了ステータスが取れない。
$ # 標準出力、標準エラーともに格納できない。 ret に true/false, $? に終了ステータスは入る $ ruby -e 'ret = system("ls -l .bashrc"); print ret, " ", $?, "\n"' \-rw-r--r-- 1 kyagi kyagi 3176 2009-05-24 00:38 .bashrc true 0 $ ruby -e 'ret = system("ls -l noExitFile"); print ret, " ", $?, "\n"' ls: noExitFileにアクセスできません: No such file or directory false 512
$ # 標準出力は格納できるが標準エラーが格納できない? $ ruby -e 'f = IO.popen("ls -l");' $ ruby -e 'f = IO.popen("ls -l noExistFile"); ls: noExistFileにアクセスできません: No such file or directory