rubyで外部コマンドを実行する方法はいくつかありますが、ライブラリなしだと標準エラーが取れなかったりと不便ですし、ライブラリを入れると小回りがきかないです。
そこで自分で作ってみました。よかったら使ってください。
ソース
require 'fileutils'
require 'tempfile'
require 'tmpdir'
require 'timeout'
class Cmd
@@timeout = 0
def Cmd.timeout=(sec) ; @@timeout = sec ; end
def Cmd.run(*cmd_array)
raise(Exception,"Command string is nil") if cmd_array.size == 0
stdout_file = Tempfile::new("#{$$}_light_cmd_tmpout",Dir.tmpdir)
stderr_file = Tempfile::new("#{$$}_light_cmd_tmperr",Dir.tmpdir)
pid = spawn(*cmd_array, :out => [stdout_file,"w"],:err => [stderr_file,"w"])
status = nil
if @@timeout != 0
begin
timeout(@@timeout) do
status = Process.waitpid2(pid)[1] >> 8
end
rescue Timeout::Error => e
begin
Process.kill('SIGINT', pid)
raise(Exception,"Timeout #{@@timeout.to_s} sec. Kill process : PID=#{pid}" )
rescue
raise(Exception,"Timeout #{@@timeout.to_s} sec. Fail to kill process : PID=#{pid}" )
end
end
else
status = Process.waitpid2(pid)[1] >> 8
end
return Result.new(stdout_file,stderr_file,status,cmd_array)
end
class Result
attr_reader :stdout_file, :stderr_file, :status, :cmd_array
def initialize(stdout_file, stderr_file, status, cmd_array)
@stdout_file = stdout_file
@stderr_file = stderr_file
@status = status
@cmd_array = cmd_array
end
def stdout ; File.read(@stdout_file) ; end
def stderr ; File.read(@stderr_file) ; end
def to_h ; {:cmd_str => cmd_str,:stdout => stdout, :stderr => stderr, :status => status} ; end
def cmd_str ; @cmd_array.join(" ") ; end
def to_s ;to_h.to_s ; end
end
class Exception < StandardError ; end
end
使い方
Cmd.runにコマンドを渡すだけです。
ret = Cmd.run("uname -r")
p ret.stdout
=> "2.6.32-220.el6.x86_64\n"
p ret.stderr
=> ""
p ret.status
=> 0
タイムアウトも仕込めます
Cmd.timeout = 1
Cmd.run("sleep 2");
=> 例外発生してくれます
長い標準出力でも、一時ファイルに吐かれているの、後から少しずつ読めます。
ret = Cmd.run("rpm -qa")
while line = ret.stdout_file.gets
puts line
sleep 1
end
=> 1行づつ出力