Module: Cheetah

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

Overview

Your swiss army knife for executing external commands in Ruby safely and conveniently.

## Features

* Easy passing of command input
* Easy capturing of command output (standard, error, or both)
* Piping commands together
* 100% secure (shell expansion is impossible by design)
* Raises exceptions on errors (no more manual status code checks)
* Optional logging for easy debugging

## Non-features

* Handling of interactive commands

Examples:

Run a command and capture its output

files = Cheetah.run("ls", "-la", :stdout => :capture)

Run a command and capture its output into a stream

File.open("files.txt", "w") do |stdout|
  Cheetah.run("ls", "-la", :stdout => stdout)
end

Run a command and handle errors

begin
  Cheetah.run("rm", "/etc/passwd")
rescue Cheetah::ExecutionFailed => e
  puts e.message
  puts "Standard output: #{e.stdout}"
  puts "Error ouptut:    #{e.stderr}"
end

Defined Under Namespace

Classes: DefaultRecorder, ExecutionFailed, NullRecorder, Recorder

Constant Summary collapse

BUILTIN_DEFAULT_OPTIONS =
{
  :stdin              => "",
  :stdout             => nil,
  :stderr             => nil,
  :logger             => nil
}
READ =
0
WRITE =
1
VERSION =

Cheetah version (uses [semantic versioning](semver.org/)).

File.read(File.dirname(__FILE__) + "/../../VERSION").strip

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.default_optionsHash

The default options of the run method. Values of options not specified in its ‘options` parameter are taken from here. If a value is not specified here too, the default value described in the run documentation is used.

By default, no values are specified here.

Examples:

Setting a logger once for execution of multiple commands

Cheetah.default_options = { :logger = my_logger }
Cheetah.run("./configure")
Cheetah.run("make")
Cheetah.run("make", "install")
Cheetah.default_options = {}

Returns:

  • (Hash)

    the default options of the run method



235
236
237
# File 'lib/cheetah.rb', line 235

def default_options
  @default_options
end

Class Method Details

.run(command, *args, options = {}) ⇒ Object .run(command_and_args, options = {}) ⇒ Object .run(*commands_and_args, options = {}) ⇒ Object

Runs external command(s) with specified arguments.

If the execution succeeds, the returned value depends on the value of the ‘:stdout` and `:stderr` options (see below). If the execution fails, the method raises an ExecutionFailed exception with detailed information about the failure. (In the single command case, the execution succeeds if the command can be executed and returns a zero exit status. In the multiple command case, the execution succeeds if the last command can be executed and returns a zero exit status.)

Commands and their arguments never undergo shell expansion — they are passed directly to the operating system. While this may create some inconvenience in certain cases, it eliminates a whole class of security bugs.

The execution can be logged using a logger passed in the ‘:logger` option. If a logger is set, the method will log the executed command(s), final exit status, passed input and both captured outputs (unless the `:stdin`, `:stdout` or `:stderr` option is set to an `IO`, which prevents logging the corresponding input or output).

The actual logging is handled by a separate object called recorder. By default, DefaultRecorder instance is used. It uses the ‘Logger::INFO` level for normal messages and the `Logger::ERROR` level for messages about errors (non-zero exit status or non-empty error output). If you need to customize the recording, you can create your own recorder (implementing the Recorder interface) and pass it in the `:recorder` option.

Values of options not set using the ‘options` parameter are taken from default_options. If a value is not specified there too, the default value described in the `options` parameter documentation is used.

Examples:

Run a command and capture its output

files = Cheetah.run("ls", "-la", :stdout => capture)

Run a command and capture its output into a stream

File.open("files.txt", "w") do |stdout|
  Cheetah.run("ls", "-la", :stdout => stdout)
end

Run a command and handle errors

begin
  Cheetah.run("rm", "/etc/passwd")
rescue Cheetah::ExecutionFailed => e
  puts e.message
  puts "Standard output: #{e.stdout}"
  puts "Error ouptut:    #{e.stderr}"
end

Overloads:

  • .run(command, *args, options = {}) ⇒ Object

    Runs a command with its arguments specified separately.

    Examples:

    Cheetah.run("tar", "xzf", "foo.tar.gz")

    Parameters:

    • command (String)

      the command to execute

    • args (Array<String>)

      the command arguments

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

      the options to execute the command with

    Options Hash (options):

    • :stdin (String, IO) — default: '') a `String` to use as command's standard input or an `IO` to read it from

      ”) a ‘String` to use as command’s standard input or an ‘IO` to read it from

    • :stdout (nil, :capture, IO) — default: nil

      specifies command’s standard output handling

      • if set to ‘nil`, ignore the output

      • if set to ‘:capture`, capture the output and return it as a string (or as the first element of a two-element array of strings if the `:stderr` option is set to `:capture` too)

      • if set to an ‘IO`, write the ouptut into it gradually as the command produces it

    • :stderr (nil, :capture, IO) — default: nil

      specifies command’s error output handling

      • if set to ‘nil`, ignore the output

      • if set to ‘:capture`, capture the output and return it as a string (or as the second element of a two-element array of strings if the `:stdout` option is set to `:capture` too)

      • if set to an ‘IO`, write the ouptut into it gradually as the command produces it

    • :logger (Logger, nil) — default: nil

      logger to log the command execution

    • :recorder (Recorder, nil) — default: DefaultRecorder.new

      recorder to handle the command execution logging

  • .run(command_and_args, options = {}) ⇒ Object

    Runs a command with its arguments specified together. This variant is useful mainly when building the command and its arguments programmatically.

    Examples:

    Cheetah.run(["tar", "xzf", "foo.tar.gz"])

    Parameters:

    • command_and_args (Array<String>)

      the command to execute (first element of the array) and its arguments (remaining elements)

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

      the options to execute the command with, same as in the first variant

  • .run(*commands_and_args, options = {}) ⇒ Object

    Runs multiple commands piped togeter. Standard output of each command execpt the last one is connected to the standard input of the next command. Error outputs are aggregated together.

    Examples:

    processes = Cheetah.run(["ps", "aux"], ["grep", "ruby"], :stdout => :capture)

    Parameters:

    • commands_and_args (Array<Array<String>>)

      the commands to execute as an array where each item is again an array containing an executed command in the first element and its arguments in the remaining ones

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

      the options to execute the commands with, same as in the first variant

Raises:



348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# File 'lib/cheetah.rb', line 348

def run(*args)
  options = args.last.is_a?(Hash) ? args.pop : {}
  options = BUILTIN_DEFAULT_OPTIONS.merge(@default_options).merge(options)

  streamed = compute_streamed(options)
  streams  = build_streams(options, streamed)
  commands = build_commands(args)
  recorder = build_recorder(options)

  recorder.record_commands(commands)

  pid, pipes = fork_commands(commands)
  select_loop(streams, pipes, recorder)
  pid, status = Process.wait2(pid)

  begin
    check_errors(commands, status, streams, streamed)
  ensure
    recorder.record_status(status)
  end

  build_result(streams, options)
end