Module: Libis::Tools::Command
- Defined in:
- lib/libis/tools/command.rb
Overview
This module allows to run an external command safely and returns it’s output, error messages and status. The run method takes any number of arguments that will be used as command-line arguments. The method returns a Hash with:
-
:out => an array with lines that were printed on the external program’s standard out.
-
:err => an array with lines that were printed on the external program’s standard error.
-
:status => exit code returned by the external program.
-
:timeout => true if the command was terminated due to a timeout.
-
:pid => pid of the command (in case <pid>.log files need to be cleaned up)
Optionally an option hash can be appended to the list of arguments with:
-
:stdin_data => values sent to the command’s standard input (optional, nothing sent if not present)
-
:binmode => if present and true, will set the IO communication to binary data
-
:timeout => if specified, SIGTERM signal is sent to the command after the number of seconds
-
:signal => Signal sent to the command instead of the default SIGTERM
-
:kill_after => if specified, SIGKILL signal is sent aftern the number of seconds if command is still running
after initial signal was sent
-
any other options will be handed over to the spawn command (e.g. pgroup)
Examples:
require 'libis/tools/command'
result = ::Libis::Tools::Command.run('ls', '-l', File.absolute_path(__FILE__))
p result # => {out: [...], err: [...], status: 0}
require 'libis/tools/command'
include ::Libis::Tools::Command
result = run('ls', '-l', File.absolute_path(__FILE__))
p result # => {out: [...], err: [...], status: 0}
Note that the Command class uses Open3#popen3 internally. All arguments supplied to Command#run are passed to the popen3 call. Unfortunately some older JRuby versions have some known issues with popen3. Please use and test carefully in JRuby environments.
Class Method Summary collapse
-
.run(*cmd, **spawn_opts) ⇒ Hash
Run an external program and return status, stdout and stderr.
Class Method Details
.run(*cmd, **spawn_opts) ⇒ Hash
Run an external program and return status, stdout and stderr.
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/libis/tools/command.rb', line 51 def self.run(*cmd, **spawn_opts) opts = { :stdin_data => spawn_opts.delete(:stdin_data) || '', :binmode => spawn_opts.delete(:binmode) || false, :timeout => spawn_opts.delete(:timeout), :signal => spawn_opts.delete(:signal) || :TERM, :kill_after => spawn_opts.delete(:kill_after), } in_r, in_w = IO.pipe out_r, out_w = IO.pipe err_r, err_w = IO.pipe in_w.sync = true if opts[:binmode] in_w.binmode out_r.binmode err_r.binmode end spawn_opts[:in] = in_r spawn_opts[:out] = out_w spawn_opts[:err] = err_w result = { :pid => nil, :status => nil, :out => [], :err => [], :timeout => false, } out_reader = nil err_reader = nil wait_thr = nil begin Timeout.timeout(opts[:timeout]) do result[:pid] = spawn(*cmd, spawn_opts) wait_thr = Process.detach(result[:pid]) in_r.close out_w.close err_w.close out_reader = Thread.new {out_r.read} err_reader = Thread.new {err_r.read} in_w.write opts[:stdin_data] in_w.close result[:status] = wait_thr.value end rescue Timeout::Error result[:timeout] = true pid = spawn_opts[:pgroup] ? -result[:pid] : result[:pid] Process.kill(opts[:signal], pid) if opts[:kill_after] unless wait_thr.join(opts[:kill_after]) Process.kill(:KILL, pid) end end rescue StandardError => e result[:err] = [e.class.name, e.] ensure result[:status] = wait_thr.value.exitstatus if wait_thr result[:out] += out_reader.value.split("\n").map(&:chomp) if out_reader result[:err] += err_reader.value.split("\n").map(&:chomp) if err_reader out_r.close unless out_r.closed? err_r.close unless err_r.closed? end result end |