2 # Wrapper around systemu that handles executing of system commands
3 # in a way that makes stdout, stderr and status available. Supports
4 # timeouts and sets a default sane environment.
6 # s = Shell.new("date", opts)
10 # puts s.status.exitstatus
12 # Options hash can have:
14 # cwd - the working directory the command will be run from
15 # stdin - a string that will be sent to stdin of the program
16 # stdout - a variable that will receive stdout, must support <<
17 # stderr - a variable that will receive stdin, must support <<
18 # environment - the shell environment, defaults to include LC_ALL=C
19 # set to nil to clear the environment even of LC_ALL
22 attr_reader :environment, :command, :status, :stdout, :stderr, :stdin, :cwd
24 def initialize(command, options={})
25 @environment = {"LC_ALL" => "C"}
33 options.each do |opt, val|
36 raise "stdout should support <<" unless val.respond_to?("<<")
40 raise "stderr should support <<" unless val.respond_to?("<<")
44 raise "stdin should be a String" unless val.is_a?(String)
48 raise "Directory #{val} does not exist" unless File.directory?(val)
55 @environment.merge!(val.dup)
61 # Actually does the systemu call passing in the correct environment, stdout and stderr
63 opts = {"env" => @environment,
68 opts["stdin"] = @stdin if @stdin
70 # Running waitpid on the cid here will start a thread
71 # with the waitpid in it, this way even if the thread
72 # that started this process gets killed due to agent
73 # timeout or such there will still be a waitpid waiting
74 # for the child to exit and not leave zombies.
75 @status = systemu(@command, opts) do |cid|
82 Log.info("Unexpected exception received while waiting for child process: #{e.class}: #{e}")