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

READ =
0
VERSION =

Cheetah version (uses semantic versioning).

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

Class Attribute Summary (collapse)

Class Method Summary (collapse)

Class Attribute Details

+ (Hash) default_options

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



202
203
204
# File 'lib/cheetah.rb', line 202

def default_options
  @default_options
end

Class Method Details

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

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:

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

    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)

      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

    • :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

  • + (Object) run(command_and_args, options = {})

    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)

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

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

    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)

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

Raises:



315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/cheetah.rb', line 315

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)
  recorder.record_stdin(streams[:stdin].string) unless streamed[:stdin]

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

  begin
    check_errors(commands, status, streams, streamed)
  ensure
    recorder.record_status(status)
    recorder.record_stdout(streams[:stdout].string) unless streamed[:stdout]
    recorder.record_stderr(streams[:stderr].string) unless streamed[:stderr]
  end

  build_result(streams, options)
end