Class: MCollective::Shell
- Inherits:
-
Object
- Object
- MCollective::Shell
- Defined in:
- lib/mcollective/shell.rb
Overview
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
Instance Attribute Summary collapse
-
#command ⇒ Object
readonly
Returns the value of attribute command.
-
#cwd ⇒ Object
readonly
Returns the value of attribute cwd.
-
#environment ⇒ Object
readonly
Returns the value of attribute environment.
-
#status ⇒ Object
readonly
Returns the value of attribute status.
-
#stderr ⇒ Object
readonly
Returns the value of attribute stderr.
-
#stdin ⇒ Object
readonly
Returns the value of attribute stdin.
-
#stdout ⇒ Object
readonly
Returns the value of attribute stdout.
-
#timeout ⇒ Object
readonly
Returns the value of attribute timeout.
Instance Method Summary collapse
-
#initialize(command, options = {}) ⇒ Shell
constructor
A new instance of Shell.
-
#runcommand ⇒ Object
Actually does the systemu call passing in the correct environment, stdout and stderr.
Constructor Details
#initialize(command, options = {}) ⇒ Shell
Returns a new instance of Shell.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/mcollective/shell.rb', line 27 def initialize(command, ={}) @environment = {"LC_ALL" => "C"} @command = command @status = nil @stdout = "" @stderr = "" @stdin = nil @cwd = Dir.tmpdir @timeout = nil .each do |opt, val| case opt.to_s when "stdout" raise "stdout should support <<" unless val.respond_to?("<<") @stdout = val when "stderr" raise "stderr should support <<" unless val.respond_to?("<<") @stderr = val when "stdin" raise "stdin should be a String" unless val.is_a?(String) @stdin = val when "cwd" raise "Directory #{val} does not exist" unless File.directory?(val) @cwd = val when "environment" if val.nil? @environment = {} else @environment.merge!(val.dup) @environment = @environment.delete_if { |k,v| v.nil? } end when "timeout" 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 ) @timeout = val end end end |
Instance Attribute Details
#command ⇒ Object (readonly)
Returns the value of attribute command.
25 26 27 |
# File 'lib/mcollective/shell.rb', line 25 def command @command end |
#cwd ⇒ Object (readonly)
Returns the value of attribute cwd.
25 26 27 |
# File 'lib/mcollective/shell.rb', line 25 def cwd @cwd end |
#environment ⇒ Object (readonly)
Returns the value of attribute environment.
25 26 27 |
# File 'lib/mcollective/shell.rb', line 25 def environment @environment end |
#status ⇒ Object (readonly)
Returns the value of attribute status.
25 26 27 |
# File 'lib/mcollective/shell.rb', line 25 def status @status end |
#stderr ⇒ Object (readonly)
Returns the value of attribute stderr.
25 26 27 |
# File 'lib/mcollective/shell.rb', line 25 def stderr @stderr end |
#stdin ⇒ Object (readonly)
Returns the value of attribute stdin.
25 26 27 |
# File 'lib/mcollective/shell.rb', line 25 def stdin @stdin end |
#stdout ⇒ Object (readonly)
Returns the value of attribute stdout.
25 26 27 |
# File 'lib/mcollective/shell.rb', line 25 def stdout @stdout end |
#timeout ⇒ Object (readonly)
Returns the value of attribute timeout.
25 26 27 |
# File 'lib/mcollective/shell.rb', line 25 def timeout @timeout end |
Instance Method Details
#runcommand ⇒ Object
Actually does the systemu call passing in the correct environment, stdout and stderr
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 |
# File 'lib/mcollective/shell.rb', line 71 def runcommand opts = {"env" => @environment, "stdout" => @stdout, "stderr" => @stderr, "cwd" => @cwd} opts["stdin"] = @stdin if @stdin thread = Thread.current # Start a double fork and exec with systemu which implies a guard thread. # If a valid timeout is configured the guard thread will terminate the # executing process and reap the pid. # If no timeout is specified the process will run to completion with the # guard thread reaping the pid on completion. @status = systemu(@command, opts) do |cid| begin if timeout.is_a?(Integer) # wait for the specified timeout sleep timeout else # sleep while the agent thread is still alive while(thread.alive?) sleep 0.1 end end # if the process is still running if (Process.kill(0, cid)) # and a timeout was specified if timeout if Util.windows? Process.kill('KILL', cid) else # Kill the process Process.kill('TERM', cid) sleep 2 Process.kill('KILL', cid) if (Process.kill(0, cid)) end end # only wait if the parent thread is dead Process.waitpid(cid) unless thread.alive? end rescue SystemExit rescue Errno::ESRCH rescue Errno::ECHILD Log.warn("Could not reap process '#{cid}'.") rescue Exception => e Log.info("Unexpected exception received while waiting for child process: #{e.class}: #{e}") end end @status.thread.kill @status end |