Module: Subprocess

Defined in:
lib/subprocess.rb,
lib/subprocess/version.rb

Overview

A Ruby clone of Python's subprocess module.

Defined Under Namespace

Classes: NonZeroExit, Process

Constant Summary collapse

PIPE =

An opaque constant that indicates that a pipe should be opened.

-1
STDOUT =

An opaque constant that can be passed to the :stderr option that indicates that the standard error stream should be redirected to the standard output.

-2
VERSION =
'1.2.0'

Class Method Summary collapse

Class Method Details

.call(cmd, opts = {}, &blk) ⇒ ::Process::Status

Note:

If you call this function with :stdout => PIPE or :stderr => PIPE, this function will block indefinitely as soon as the OS's pipe buffer fills up, as neither file descriptor will be read from. To avoid this, use Subprocess::Process#communicate from a passed block.

Call and wait for the return of a given process.

Returns:

  • (::Process::Status)

    The exit status of the process

See Also:

  • {Process{Process#initialize}

34
35
36
# File 'lib/subprocess.rb', line 34

def self.call(cmd, opts={}, &blk)
  Process.new(cmd, opts, &blk).wait
end

.check_call(cmd, opts = {}, &blk) ⇒ ::Process::Status

Note:

If you call this function with :stdout => PIPE or :stderr => PIPE, this function will block indefinitely as soon as the OS's pipe buffer fills up, as neither file descriptor will be read from. To avoid this, use Subprocess::Process#communicate from a passed block.

Like call, except raise a NonZeroExit if the process did not terminate successfully.

Examples:

Grep a file for a string

Subprocess.check_call(%W{grep -q llama ~/favorite_animals})

Communicate with a child process

Subprocess.check_call(%W{sendmail -t}, :stdin => Subprocess::PIPE) do |p|
  p.communicate <<-EMAIL
From: [email protected]
To: [email protected]
Subject: I am so fluffy.

SO FLUFFY!
http://upload.wikimedia.org/wikipedia/commons/3/3e/Unshorn_alpaca_grazing.jpg
  EMAIL
end

Returns:

  • (::Process::Status)

    The exit status of the process

Raises:

  • (NonZeroExit)

    if the process returned a non-zero exit status (i.e., was terminated with an error or was killed by a signal)

See Also:

  • {Process{Process#initialize}

66
67
68
69
70
# File 'lib/subprocess.rb', line 66

def self.check_call(cmd, opts={}, &blk)
  status = Process.new(cmd, opts, &blk).wait
  raise NonZeroExit.new(cmd, status) unless status.success?
  status
end

.check_output(cmd, opts = {}, &blk) ⇒ String

Like check_call, but return the contents of stdout, much like Kernel#system.

Examples:

Get the system load

system_load = Subprocess.check_output(['uptime']).split(' ').last(3)

Returns:

  • (String)

    The contents of stdout

Raises:

  • (NonZeroExit)

    if the process returned a non-zero exit status (i.e., was terminated with an error or was killed by a signal)

See Also:

  • {Process{Process#initialize}

83
84
85
86
87
88
89
# File 'lib/subprocess.rb', line 83

def self.check_output(cmd, opts={}, &blk)
  opts[:stdout] = PIPE
  child = Process.new(cmd, opts, &blk)
  output, _ = child.communicate()
  raise NonZeroExit.new(cmd, child.status) unless child.wait.success?
  output
end

.popen(cmd, opts = {}, &blk) ⇒ Process

An alias for Process.new. Mostly here to better emulate the Python API.

Returns:

  • (Process)

    A process with the given arguments


20
21
22
# File 'lib/subprocess.rb', line 20

def self.popen(cmd, opts={}, &blk)
  Process.new(cmd, opts, &blk)
end

.status_to_s(status, convert_high_exit = true) ⇒ String

Print a human readable interpretation of a process exit status.

Parameters:

  • status (::Process::Status)

    The status returned by waitpid2.

  • convert_high_exit (Boolean) (defaults to: true)

    Whether to convert exit statuses greater than 128 into the usual convention for exiting after trapping a signal. (e.g. many programs will exit with status 130 after receiving a SIGINT / signal 2.)

Returns:

  • (String)

    Text interpretation


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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/subprocess.rb', line 100

def self.status_to_s(status, convert_high_exit=true)
  # use an array just in case we somehow get a status with all the bits set
  parts = []
  if status.exited?
    parts << "exited with status #{status.exitstatus}"
    if convert_high_exit && status.exitstatus > 128
      # convert high exit statuses into what the original signal may have
      # been according to the usual exit status convention
      sig_num = status.exitstatus - 128

      # sigh, why is ruby so silly
      if Signal.respond_to?(:signame)
        # ruby 2.0 way
        sig_name = Signal.signame(sig_num)
      elsif Signal.list.respond_to?(:key)
        # ruby 1.9 way
        sig_name = Signal.list.key(sig_num)
      else
        # ruby 1.8 way
        sig_name = Signal.list.index(sig_num)
      end

      if sig_name
        parts << "(maybe SIG#{sig_name})"
      end
    end
  end
  if status.signaled?
    parts << "killed by signal #{status.termsig}"
  end
  if status.stopped?
    parts << "stopped by signal #{status.stopsig}"
  end

  if parts.empty?
    raise ArgumentError.new("Don't know how to interpret #{status.inspect}")
  end

  parts.join(', ')
end