Module: ProcessExecuter

Defined in:
lib/process_executer.rb,
lib/process_executer/errors.rb,
lib/process_executer/result.rb,
lib/process_executer/options.rb,
lib/process_executer/version.rb,
lib/process_executer/commands.rb,
lib/process_executer/commands/run.rb,
lib/process_executer/destinations.rb,
lib/process_executer/options/base.rb,
lib/process_executer/monitored_pipe.rb,
lib/process_executer/destinations/io.rb,
lib/process_executer/destinations/tee.rb,
lib/process_executer/destinations/close.rb,
lib/process_executer/destinations/stderr.rb,
lib/process_executer/destinations/stdout.rb,
lib/process_executer/destinations/writer.rb,
lib/process_executer/options/run_options.rb,
lib/process_executer/result_with_capture.rb,
lib/process_executer/options/spawn_options.rb,
lib/process_executer/destinations/file_path.rb,
lib/process_executer/commands/run_with_capture.rb,
lib/process_executer/options/option_definition.rb,
lib/process_executer/commands/spawn_with_timeout.rb,
lib/process_executer/destinations/file_path_mode.rb,
lib/process_executer/destinations/monitored_pipe.rb,
lib/process_executer/destinations/file_descriptor.rb,
lib/process_executer/destinations/destination_base.rb,
lib/process_executer/destinations/child_redirection.rb,
lib/process_executer/options/run_with_capture_options.rb,
lib/process_executer/destinations/file_path_mode_perms.rb,
lib/process_executer/options/spawn_with_timeout_options.rb

Overview

The ProcessExecuter module provides extended versions of Process.spawn that block while the command is executing. These methods provide enhanced features such as timeout handling, more flexible redirection options, logging, error raising, and output capturing.

The interface of these methods is the same as the standard library Process.spawn method, but with additional options and features.

These methods are:

See the Error class for the error architecture for this module.

Defined Under Namespace

Modules: Commands, Destinations, Options Classes: ArgumentError, CommandError, Error, FailedError, MonitoredPipe, ProcessIOError, Result, ResultWithCapture, SignaledError, SpawnError, TimeoutError

Constant Summary collapse

VERSION =

The current Gem version

Returns:

  • (String)
'4.0.0'

Class Method Summary collapse

Class Method Details

.run(*command, **options_hash) ⇒ ProcessExecuter::Result .run(*command, options) ⇒ ProcessExecuter::Result

Extends spawn_with_timeout, adding more flexible redirection and other options

Accepts all Process.spawn execution options, the additional options defined by spawn_with_timeout, and the additional options raise_errors and logger:

  • raise_errors: <Boolean> makes execution errors an exception if true (default is true)
  • logger: <Logger> logs the command and its result at :info level using the given logger (default is not to log)

Internally, this method wraps stdout and stderr redirection options in a MonitoredPipe, enabling more flexible output handling. It allows any object that responds to #write to be used as a destination and supports multiple destinations using the form [:tee, destination, ...].

When the command exits with a non-zero exit status or does not exit normally, one of the following errors will be raised unless the option raise_errors: false is explicitly given:

These errors all have a result attribute that contains the Result object for this command.

If raise_errors: false is given and there was an error, the returned Result object indicates what the error is via its success?, signaled?, or timed_out? attributes.

A ProcessIOError is raised if an exception occurs while collecting subprocess output.

Giving the option raise_errors: false will not suppress ProcessIOError, SpawnError, or ArgumentError errors.

Examples:

capture stdout to a StringIO buffer

out_buffer = StringIO.new
result = ProcessExecuter.run('echo HELLO', out: out_buffer)
out_buffer.string #=> "HELLO\n"

with :raise_errors set to true

begin
  result = ProcessExecuter.run('exit 1', raise_errors: true)
rescue ProcessExecuter::FailedError => e
  e.result.exitstatus #=> 1
end

with :raise_errors set to false

result = ProcessExecuter.run('exit 1', raise_errors: false)
result.exitstatus #=> 1

with a logger

logger_buffer = StringIO.new
logger = Logger.new(logger_buffer, level: :info)
result = ProcessExecuter.run('echo HELLO', logger: logger)
logger_buffer.string #=> "INFO -- : PID 5555: [\"echo HELLO\"] exited with status pid 5555 exit 0\n"

Overloads:

Returns:

Raises:



252
253
254
255
# File 'lib/process_executer.rb', line 252

def self.run(*command, **options_hash)
  command, options = command_and_options(Options::RunOptions, command, options_hash)
  ProcessExecuter::Commands::Run.new(command, options).call
end

.run_with_capture(*command, **options_hash) ⇒ ProcessExecuter::ResultWithCapture .run_with_capture(*command, options) ⇒ ProcessExecuter::ResultWithCapture

Extends run, automatically capturing stdout and stderr

Accepts all Process.spawn execution options, the additional options defined by spawn_with_timeout and run, and the additional options merge_output, encoding, stdout_encoding, and stderr_encoding:

  • merge_output: <Boolean> if true merges stdout and stderr into a single capture buffer (default is false)
  • encoding: <Encoding> sets the encoding for both stdout and stderr captures (default is Encoding::UTF_8)
  • stdout_encoding: <Encoding> sets the encoding for the stdout capture and, if not nil, overrides the encoding option for stdout (default is nil)
  • stderr_encoding: <Encoding> sets the encoding for the stderr capture and, if not nil, overrides the encoding option for stderr (default is nil)

The captured output is accessed in the returned object's #stdout and #stderr methods. Merged output (if the merged_output: true option is given) is accessed in the #stdout method.

stdout and stderr redirection destinations may be given by the user (e.g. out: <destination> or err: <destination>). These redirections will receive the output in addition to the internal capture.

Unless told otherwise, the internally captured output is assumed to be in UTF-8 encoding. This assumption can be changed with the encoding, stdout_encoding, or stderr_encoding options. These options accept any encoding objects returned by Encoding.list or their String equivalent given by #to_s.

The bytes captured are not transcoded. They are interpreted as being in the specified encoding. The user will have to check the validity of the encoding by calling #valid_encoding? on the captured output (e.g., result.stdout.valid_encoding?).

A ProcessExecuter::ArgumentError will be raised if both an options object and an options_hash are given.

Examples:

capture stdout and stderr

result =
ProcessExecuter.run_with_capture('echo HELLO; echo ERROR >&2')
result.stdout #=> "HELLO\n" result.stderr #=> "ERROR\n"

merge stdout and stderr

result = ProcessExecuter.run_with_capture('echo HELLO; echo ERROR >&2', merge_output: true)
# order of output is not guaranteed
result.stdout #=> "HELLO\nERROR\n" result.stderr #=> ""

default encoding

result = ProcessExecuter.run_with_capture('echo HELLO')
result.stdout #=> "HELLO\n"
result.stdout.encoding #=> #<Encoding:UTF-8>
result.stdout.valid_encoding? #=> true

custom encoding

result = ProcessExecuter.run_with_capture('echo HELLO', encoding: Encoding::ISO_8859_1)
result.stdout #=> "HELLO\n"
result.stdout.encoding #=> #<Encoding:ISO-8859-1>
result.stdout.valid_encoding? #=> true

custom encoding with invalid bytes

File.binwrite('output.txt', "\xFF\xFE") # little-endian BOM marker is not valid UTF-8
result = ProcessExecuter.run_with_capture('cat output.txt')
result.stdout #=> "\xFF\xFE"
result.stdout.encoding #=> #<Encoding:UTF-8>
result.stdout.valid_encoding? #=> false

Overloads:

Returns:

  • (ProcessExecuter::ResultWithCapture)

    Where #stdout and #stderr are strings whose encoding is determined by the :encoding, :stdout_encoding, or :stderr_encoding options.

Raises:



400
401
402
403
# File 'lib/process_executer.rb', line 400

def self.run_with_capture(*command, **options_hash)
  command, options = command_and_options(Options::RunWithCaptureOptions, command, options_hash)
  ProcessExecuter::Commands::RunWithCapture.new(command, options).call
end

.spawn_with_timeout(*command, **options_hash) ⇒ ProcessExecuter::Result .spawn_with_timeout(*command, options) ⇒ ProcessExecuter::Result

Extends Process.spawn to run command and wait (with timeout) for it to finish

Accepts all Process.spawn execution options and the additional option timeout_after:

  • timeout_after: <Numeric, nil>: the amount of time (in seconds) to wait before signaling the process with SIGKILL. 0 or nil means no timeout.

Returns a Result object. The Result class is a decorator for Process::Status that provides additional attributes about the command's status. This includes the command that was run, the options used to run it, elapsed_time of the command, and whether the command timed_out?.

Examples:

command line given as a single string

result = ProcessExecuter.spawn_with_timeout('echo "3\n2\n1" | sort')
result.exited? # => true
result.success? # => true
result.exitstatus # => 0
result.timed_out? # => false

command given as an exe_path and args

result = ProcessExecuter.spawn_with_timeout('ping', '-c', '1', 'localhost')

with a timeout

result = ProcessExecuter.spawn_with_timeout('sleep 10', timeout_after: 0.01)
result.exited? # => false
result.success? # => nil
result.signaled? # => true
result.termsig # => 9
result.timed_out? # => true

with a env hash

env = { 'EXITSTATUS' => '1' }
result = ProcessExecuter.spawn_with_timeout(env, 'exit $EXITSTATUS')
result.success? # => false
result.exitstatus # => 1

capture stdout to a StringIO buffer

stdout_buffer = StringIO.new
stdout_pipe = ProcessExecuter::MonitoredPipe.new(stdout_buffer)
begin
  result = ProcessExecuter.spawn_with_timeout('echo "3\n2\n1" | sort', out: stdout_pipe)
  stdout_buffer.string # => "1\n2\n3\n"
ensure
  stdout_pipe.close
end

Overloads:

Returns:

Raises:

  • (ProcessExecuter::ArgumentError)

    If the command or an option is not valid

    Raised if an invalid option key or value is given, or both an options object and options_hash are given.

  • (ProcessExecuter::SpawnError)

    Process.spawn raised an error before the command was run

    Raised if the Process.spawn method raises an error before the command is run.



122
123
124
125
# File 'lib/process_executer.rb', line 122

def self.spawn_with_timeout(*command, **options_hash)
  command, options = command_and_options(Options::SpawnWithTimeoutOptions, command, options_hash)
  ProcessExecuter::Commands::SpawnWithTimeout.new(command, options).call
end