Wrapper around systemu that handles executing of system commands in a way that makes stdout, stderr and status available. Supports timeouts and sets a default sane environment.
s = Shell.new("date", opts)
s.runcommand
puts s.stdout
puts s.stderr
puts s.status.exitstatus
Options hash can have:
cwd - the working directory the command will be run from
stdin - a string that will be sent to stdin of the program
stdout - a variable that will receive stdout, must support <<
stderr - a variable that will receive stdin, must support <<
environment - the shell environment, defaults to include LC_ALL=C
set to nil to clear the environment even of LC_ALL
timeout - a timeout in seconds after which the subprocess is killed,
the special value :on_thread_exit kills the subprocess
when the invoking thread (typically the agent) has ended
(Not documented)
# File lib/mcollective/shell.rb, line 27
27: def initialize(command, options={})
28: @environment = {"LC_ALL" => "C"}
29: @command = command
30: @status = nil
31: @stdout = ""
32: @stderr = ""
33: @stdin = nil
34: @cwd = Dir.tmpdir
35: @timeout = nil
36:
37: options.each do |opt, val|
38: case opt.to_s
39: when "stdout"
40: raise "stdout should support <<" unless val.respond_to?("<<")
41: @stdout = val
42:
43: when "stderr"
44: raise "stderr should support <<" unless val.respond_to?("<<")
45: @stderr = val
46:
47: when "stdin"
48: raise "stdin should be a String" unless val.is_a?(String)
49: @stdin = val
50:
51: when "cwd"
52: raise "Directory #{val} does not exist" unless File.directory?(val)
53: @cwd = val
54:
55: when "environment"
56: if val.nil?
57: @environment = {}
58: else
59: @environment.merge!(val.dup)
60: end
61: when "timeout"
62: raise "timeout should be a positive integer or the symbol :on_thread_exit symbol" unless val.eql?(:on_thread_exit) || ( val.is_a?(Fixnum) && val>0 )
63: @timeout = val
64: end
65: end
66: end
Actually does the systemu call passing in the correct environment, stdout and stderr
# File lib/mcollective/shell.rb, line 69
69: def runcommand
70: opts = {"env" => @environment,
71: "stdout" => @stdout,
72: "stderr" => @stderr,
73: "cwd" => @cwd}
74:
75: opts["stdin"] = @stdin if @stdin
76:
77:
78: thread = Thread.current
79: # Start a double fork and exec with systemu which implies a guard thread.
80: # If a valid timeout is configured the guard thread will terminate the
81: # executing process and reap the pid.
82: # If no timeout is specified the process will run to completion with the
83: # guard thread reaping the pid on completion.
84: @status = systemu(@command, opts) do |cid|
85: begin
86: if timeout.is_a?(Fixnum)
87: # wait for the specified timeout
88: sleep timeout
89: else
90: # sleep while the agent thread is still alive
91: while(thread.alive?)
92: sleep 0.1
93: end
94: end
95:
96: # if the process is still running
97: if (Process.kill(0, cid))
98: # and a timeout was specified
99: if timeout
100: if Util.windows?
101: Process.kill('KILL', cid)
102: else
103: # Kill the process
104: Process.kill('TERM', cid)
105: sleep 2
106: Process.kill('KILL', cid) if (Process.kill(0, cid))
107: end
108: end
109: # only wait if the parent thread is dead
110: Process.waitpid(cid) unless thread.alive?
111: end
112: rescue SystemExit
113: rescue Errno::ESRCH
114: rescue Errno::ECHILD
115: Log.warn("Could not reap process '#{cid}'.")
116: rescue Exception => e
117: Log.info("Unexpected exception received while waiting for child process: #{e.class}: #{e}")
118: end
119: end
120: @status.thread.kill
121: @status
122: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.