Method: Libis::Tools::Command.run

Defined in:
lib/libis/tools/command.rb

.run(*cmd) ⇒ Hash

Run an external program and return status, stdout and stderr.

Parameters:

  • cmd (Array<String>)

    command name optionally prepended with env and appended with command-line arguments

Returns:

  • (Hash)

    a Hash with:

    • :status (Integer) - the exit status of the command

    • :out (Array<String>) - the stdout output of the command

    • :err (Array<String>)- the stderr output of the command

    • :timeout(Boolean) - if true, the command did not return in time

    • :pid(Integer) - the command’s processID



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
128
# File 'lib/libis/tools/command.rb', line 51

def self.run(*cmd)

  spawn_opts = Hash === cmd.last ? cmd.pop.dup : {}
  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.message]

  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