class MCollective::Shell
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
Attributes
command[R]
cwd[R]
environment[R]
status[R]
stderr[R]
stdin[R]
stdout[R]
timeout[R]
Public Class Methods
new(command, options={})
click to toggle source
# File lib/mcollective/shell.rb 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 @environment = @environment.delete_if { |k,v| v.nil? } 61 end 62 63 when "timeout" 64 raise "timeout should be a positive integer or the symbol :on_thread_exit symbol" unless val.eql?(:on_thread_exit) || ( val.is_a?(Integer) && val>0 ) 65 @timeout = val 66 end 67 end 68 end
Public Instance Methods
runcommand()
click to toggle source
Actually does the systemu call passing in the correct environment, stdout and stderr
# File lib/mcollective/shell.rb 71 def runcommand 72 opts = {"env" => @environment, 73 "stdout" => @stdout, 74 "stderr" => @stderr, 75 "cwd" => @cwd} 76 77 opts["stdin"] = @stdin if @stdin 78 79 80 thread = Thread.current 81 # Start a double fork and exec with systemu which implies a guard thread. 82 # If a valid timeout is configured the guard thread will terminate the 83 # executing process and reap the pid. 84 # If no timeout is specified the process will run to completion with the 85 # guard thread reaping the pid on completion. 86 @status = systemu(@command, opts) do |cid| 87 begin 88 if timeout.is_a?(Integer) 89 # wait for the specified timeout 90 sleep timeout 91 else 92 # sleep while the agent thread is still alive 93 while(thread.alive?) 94 sleep 0.1 95 end 96 end 97 98 # if the process is still running 99 if (Process.kill(0, cid)) 100 # and a timeout was specified 101 if timeout 102 if Util.windows? 103 Process.kill('KILL', cid) 104 else 105 # Kill the process 106 Process.kill('TERM', cid) 107 sleep 2 108 Process.kill('KILL', cid) if (Process.kill(0, cid)) 109 end 110 end 111 # only wait if the parent thread is dead 112 Process.waitpid(cid) unless thread.alive? 113 end 114 rescue SystemExit 115 rescue Errno::ESRCH 116 rescue Errno::ECHILD 117 Log.warn("Could not reap process '#{cid}'.") 118 rescue Exception => e 119 Log.info("Unexpected exception received while waiting for child process: #{e.class}: #{e}") 120 end 121 end 122 @status.thread.kill 123 @status 124 end