module POSIX::Spawn
def `(cmd)
is compatible with Kernel#`.
and returns anything written to the new process's stdout. This method
Executes a command in a subshell using the system's shell interpreter
def `(cmd) r, w = IO.pipe command_and_args = system_command_prefixes + [cmd, {:out => w, r => :close}] pid = spawn(*command_and_args) if pid > 0 w.close out = r.read ::Process.waitpid(pid) out else '' end ensure [r, w].each{ |io| io.close rescue nil } end
def adjust_process_spawn_argv(args)
['echo', 'fuuu'], 'hello' => [['echo', 'fuuu'], 'hello']
'echo hello world' => [['/bin/sh', '/bin/sh'], '-c', 'echo hello world']
'echo', 'hello', 'world' => [['echo', 'echo'], 'hello', 'world']
'true' => [['true', 'true']]
The args array may follow any of these variations:
to be run through the shell (single argument strings with spaces).
standard argv suitable for use with exec. This includes detecting commands
Converts the various supported command argument variations into a
def adjust_process_spawn_argv(args) if args.size == 1 && args[0].is_a?(String) && args[0] =~ /[ |>]/ # single string with these characters means run it through the shell command_and_args = system_command_prefixes + [args[0]] [*command_and_args] elsif !args[0].respond_to?(:to_ary) # [argv0, argv1, ...] [[args[0], args[0]], *args[1..-1]] else # [[cmdname, argv0], argv1, ...] args end end
def default_file_reopen_info(fd, file)
file - The string path to the file that fd should be redirected to.
streams.
object, integer fd number, or :in, :out, :err for one of the standard
fd - The file descriptor that is being redirected. This may be an IO
stderr default to write, while stdin and all other fds default to read.
default flags vary based on the what fd is being redirected. stdout and
The default [file, flags, mode] tuple for a given fd and filename. The
def default_file_reopen_info(fd, file) case fd when :in, STDIN, $stdin, 0 [file, "r", 0644] when :out, STDOUT, $stdout, 1 [file, "w", 0644] when :err, STDERR, $stderr, 2 [file, "w", 0644] else [file, "r", 0644] end end
def extract_process_spawn_arguments(*args)
Returns an [env, argv, options] tuple. All elements are guaranteed to be
argv.
number of strings or an Array full of strings that make up the new process's
The env and options hashes are optional. The command may be a variable
Process::spawn([env], command, ..., [options])
The following method signature is supported:
extension functions.
simple [env, argv, options] tuple. This just makes life easier for the
Turns the various varargs incantations supported by Process::spawn into a
def extract_process_spawn_arguments(*args) # pop the options hash off the end if it's there options = if args[-1].respond_to?(:to_hash) args.pop.to_hash else {} end flatten_process_spawn_options!(options) normalize_process_spawn_redirect_file_options!(options) # shift the environ hash off the front if it's there and account for # possible :env key in options hash. env = if args[0].respond_to?(:to_hash) args.shift.to_hash else {} end env.merge!(options.delete(:env)) if options.key?(:env) # remaining arguments are the argv supporting a number of variations. argv = adjust_process_spawn_argv(args) [env, argv, options] end
def fd?(object)
Returns true if object is an instance of IO, Integer >= 0, or one of the
Determine whether object is fd-like.
def fd?(object) case object when Integer object >= 0 when :in, :out, :err, STDIN, STDOUT, STDERR, $stdin, $stdout, $stderr, IO true else object.respond_to?(:to_io) && !object.to_io.nil? end end
def fd_to_io(object)
Convert a fd identifier to an IO object.
def fd_to_io(object) case object when STDIN, STDOUT, STDERR, $stdin, $stdout, $stderr object when :in, 0 STDIN when :out, 1 STDOUT when :err, 2 STDERR when Integer object >= 0 ? IO.for_fd(object) : nil when IO object else object.respond_to?(:to_io) ? object.to_io : nil end end
def flatten_process_spawn_options!(options)
options - The options hash. This is modified in place.
spawn implementations.
like: { fd1 => :close, fd2 => :close }. This just makes life easier for the
Convert { [fd1, fd2, ...] => (:close|fd) } options to individual keys,
def flatten_process_spawn_options!(options) options.to_a.each do |key, value| if key.respond_to?(:to_ary) key.to_ary.each { |fd| options[fd] = value } options.delete(key) end end end
def fspawn(*args)
Ruby fork + exec. Supports the standard spawn interface as described in
Spawn a child process with a variety of options using a pure
def fspawn(*args) env, argv, options = extract_process_spawn_arguments(*args) valid_options = [:chdir, :unsetenv_others, :pgroup] if badopt = options.find{ |key,val| !fd?(key) && !valid_options.include?(key) } raise ArgumentError, "Invalid option: #{badopt[0].inspect}" elsif !argv.is_a?(Array) || !argv[0].is_a?(Array) || argv[0].size != 2 raise ArgumentError, "Invalid command name" end fork do begin # handle FD => {FD, :close, [file,mode,perms]} options options.each do |key, val| if fd?(key) key = fd_to_io(key) if fd?(val) val = fd_to_io(val) key.reopen(val) if key.respond_to?(:close_on_exec=) key.close_on_exec = false val.close_on_exec = false end elsif val == :close if key.respond_to?(:close_on_exec=) key.close_on_exec = true else key.close end elsif val.is_a?(Array) file, mode_string, perms = *val key.reopen(File.open(file, mode_string, perms)) end end end # setup child environment ENV.replace({}) if options[:unsetenv_others] == true env.each { |k, v| ENV[k] = v } # { :chdir => '/' } in options means change into that dir ::Dir.chdir(options[:chdir]) if options[:chdir] # { :pgroup => pgid } options pgroup = options[:pgroup] pgroup = 0 if pgroup == true Process::setpgid(0, pgroup) if pgroup # do the deed if RUBY_VERSION =~ /\A1\.8/ ::Kernel::exec(*argv) else argv_and_options = argv + [{:close_others=>false}] ::Kernel::exec(*argv_and_options) end ensure exit!(127) end end end
def normalize_process_spawn_redirect_file_options!(options)
STDIN => '/some/file' => ['/some/file', 'r', 0644]
:err => '/some/file' => ['/some/file', 'w', 0644]
:out => '/some/file' => ['/some/file', 'w', 0644]
:in => '/some/file' => ['/some/file', 'r', 0644]
Convert variations of redirecting to a file to a standard tuple.
def normalize_process_spawn_redirect_file_options!(options) options.to_a.each do |key, value| next if !fd?(key) # convert string and short array values to if value.respond_to?(:to_str) value = default_file_reopen_info(key, value) elsif value.respond_to?(:to_ary) && value.size < 3 defaults = default_file_reopen_info(key, value[0]) value += defaults[value.size..-1] else value = nil end # replace string open mode flag maybe and replace original value if value value[1] = OFLAGS[value[1]] if value[1].respond_to?(:to_str) options[key] = value end end end
def popen4(*argv)
when finished and the child process's status must be collected by a call
readable IO objects. The caller should take care to close all IO objects
process's pid, stdin is a writeable IO object, and stdout / stderr are
Returns a [pid, stdin, stdout, stderr] tuple, where pid is the new
in the POSIX::Spawn module documentation.
the spawning process. Supports the standard spawn interface as described
Spawn a child process with all standard IO streams piped in and out of
def popen4(*argv) # create some pipes (see pipe(2) manual -- the ruby docs suck) ird, iwr = IO.pipe ord, owr = IO.pipe erd, ewr = IO.pipe # spawn the child process with either end of pipes hooked together opts = ((argv.pop if argv[-1].is_a?(Hash)) || {}).merge( # redirect fds # close other sides :in => ird, iwr => :close, :out => owr, ord => :close, :err => ewr, erd => :close ) pid = spawn(*(argv + [opts])) [pid, iwr, ord, erd] ensure # we're in the parent, close child-side fds [ird, owr, ewr].each { |fd| fd.close if fd } end
def pspawn(*args)
Raises NotImplementedError when the posix_spawn_ext module could not be
the POSIX::Spawn module documentation.
systems interfaces. Supports the standard spawn interface as described in
Spawn a child process with a variety of options using the posix_spawn(2)
def pspawn(*args) env, argv, options = extract_process_spawn_arguments(*args) raise NotImplementedError unless respond_to?(:_pspawn) if defined? JRUBY_VERSION # On the JVM, changes made to the environment are not propagated down # to C via get/setenv, so we have to fake it here. unless options[:unsetenv_others] == true env = ENV.merge(env) options[:unsetenv_others] = true end end _pspawn(env, argv, options) end
def spawn(*args)
Returns the integer pid of the newly spawned process.
new child process.
options - Optional hash of operations to perform before executing the
argvN - Zero or more string program arguments (argv).
program to execute.
command - A string command name, or shell program, used to determine the
env - Optional hash specifying the new process's environment.
spawn([env], command, [argv1, ...], [options])
available implementation for the current platform and Ruby version.
Spawn a child process with a variety of options using the best
def spawn(*args) if respond_to?(:_pspawn) pspawn(*args) elsif ::Process.respond_to?(:spawn) ::Process::spawn(*args) else fspawn(*args) end end
def system(*args)
Returns true if the command returns a zero exit status, or false for
This method is compatible with Kernel#system.
described in the POSIX::Spawn module documentation.
status is available as $?. Supports the standard spawn interface as
Executes a command and waits for it to complete. The command's exit
def system(*args) pid = spawn(*args) return false if pid <= 0 ::Process.waitpid(pid) $?.exitstatus == 0 rescue Errno::ENOENT false end
def system_command_prefixes
[['/bin/sh', '/bin/sh'], '-c']
On all other systems, this will yield:
than 'cmd.exe', specify its path in ENV['COMSPEC']
is not specified. If you would like to use something other
Note: 'cmd.exe' is used if the COMSPEC environment variable
[['cmd.exe', 'cmd.exe'], '/c']
On a Windows machine, this will yield:
Derives the shell command to use when running the spawn.
def system_command_prefixes if RUBY_PLATFORM =~ /(mswin|mingw|cygwin|bccwin)/ sh = ENV['COMSPEC'] || 'cmd.exe' [[sh, sh], '/c'] else [['/bin/sh', '/bin/sh'], '-c'] end end