Method: Subprocess::Process#initialize

Defined in:
lib/subprocess.rb

#initialize(cmd, opts = {}) {|process| ... } ⇒ Process

Create a new process.

Parameters:

  • cmd (Array<String>)

    The command to run and its arguments (in the style of an argv array). Unlike Python’s subprocess module, cmd cannnot be a String.

  • opts (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opts):

  • :stdin (IO, Fixnum, String, Subprocess::PIPE, nil)

    The IO, file descriptor number, or file name to use for the process’s standard input. If the magic value Subprocess::PIPE is passed, a new pipe will be opened.

  • :stdout (IO, Fixnum, String, Subprocess::PIPE, nil)

    The IO, file descriptor number, or file name to use for the process’s standard output. If the magic value Subprocess::PIPE is passed, a pipe will be opened and attached to the process.

  • :stderr (IO, Fixnum, String, Subprocess::PIPE, Subprocess::STDOUT, nil)

    The IO, file descriptor number, or file name to use for the process’s standard error. If the special value Subprocess::PIPE is passed, a pipe will be opened and attached to the process. If the special value STDOUT is passed, the process’s stderr will be redirected to its stdout (much like bash’s ‘2>&1`).

  • :cwd (String)

    The directory to change to before executing the child process.

  • :env (Hash<String, String>)

    The environment to use in the child process.

  • :retain_fds (Array<Fixnum>)

    An array of file descriptor numbers that should not be closed before executing the child process. Note that, unlike Python (which has :close_fds defaulting to false), all file descriptors not specified here will be closed.

  • :exec_opts (Hash)

    A hash that will be merged into the options hash of the call to Kernel#exec.

  • :preexec_fn (Proc)

    A function that will be called in the child process immediately before executing cmd.

Yields:

  • (process)

    Yields the just-spawned Subprocess::Process to the optional block. This occurs after all of Subprocess::Process‘s error handling has been completed, and is a great place to call #communicate, especially when used in conjunction with check_call.

Yield Parameters:

  • process (Process)

    The process that was just spawned.

Raises:

  • (ArgumentError)


226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/subprocess.rb', line 226

def initialize(cmd, opts={}, &blk)
  raise ArgumentError, "cmd must be an Array" unless Array === cmd
  raise ArgumentError, "cmd cannot be empty" if cmd.empty?

  @command = cmd

  # Figure out what file descriptors we should pass on to the child (and
  # make externally visible ourselves)
  @child_stdin, @stdin = parse_fd(opts[:stdin], 'r')
  @child_stdout, @stdout = parse_fd(opts[:stdout], 'w')
  unless opts[:stderr] == STDOUT
    @child_stderr, @stderr = parse_fd(opts[:stderr], 'w')
  end

  retained_fds = Set.new(opts[:retain_fds] || [])

  # A control pipe for ferrying errors back from the child
  control_r, control_w = IO.pipe

  @pid = fork do
    begin
      FileUtils.cd(opts[:cwd]) if opts[:cwd]

      ::STDIN.reopen(@child_stdin) if @child_stdin
      ::STDOUT.reopen(@child_stdout) if @child_stdout
      if opts[:stderr] == STDOUT
        ::STDERR.reopen(::STDOUT)
      else
        ::STDERR.reopen(@child_stderr) if @child_stderr
      end

      # Set up a new environment if we're requested to do so.
      if opts[:env]
        ENV.clear
        ENV.update(opts[:env])
      end

      # Call the user back, maybe?
      opts[:preexec_fn].call if opts[:preexec_fn]

      options = {close_others: true}.merge(opts.fetch(:exec_opts, {}))
      if opts[:retain_fds]
        retained_fds.each { |fd| options[fd] = fd }
      end

      # Ruby's Kernel#exec will call an exec(3) variant if called with two
      # or more arguments, but when called with just a single argument will
      # spawn a subshell with that argument as the command. Since we always
      # want to call exec(3), we use the third exec form, which passes a
      # [cmdname, argv0] array as its first argument and never invokes a
      # subshell.
      exec([cmd[0], cmd[0]], *cmd[1..-1], options)

    rescue Exception => e
      # Dump all errors up to the parent through the control socket
      Marshal.dump(e, control_w)
      control_w.flush
    end

    # Something has gone terribly, terribly wrong if we're hitting this :(
    exit!(1)
  end

  # Meanwhile, in the parent process...

  # First, let's close some things we shouldn't have access to
  @child_stdin.close if our_fd?(opts[:stdin])
  @child_stdout.close if our_fd?(opts[:stdout])
  @child_stderr.close if our_fd?(opts[:stderr])
  control_w.close

  # Any errors during the spawn process? We'll get past this point when the
  # child execs and the OS closes control_w
  begin
    e = Marshal.load(control_r)
    e = "Unknown Failure" unless e.is_a?(Exception) || e.is_a?(String)
    # Because we're throwing an exception and not returning a
    # Process, we need to make sure the child gets reaped
    wait
    raise e
  rescue EOFError # Nothing to read? Great!
  ensure
    control_r.close
  end

  # Everything is okay. Good job, team!
  blk.call(self) if blk
end