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 # Check if the parent thread is alive. If it should die,
71 # and the process spawned by systemu is still alive,
72 # fire off a blocking waitpid and wait for the process to
73 # finish so that we can avoid zombies.
74 thread = Thread.current
75 @status = systemu(@command, opts) do |cid|
81 Process.waitpid(cid) if Process.getpgid(cid)
86 Log.info("Unexpected exception received while waiting for child process: #{e.class}: #{e}")