Module Cheetah
In: lib/cheetah/version.rb
lib/cheetah.rb

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

@example Run a command and capture its output

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

@example Run a command and capture its output into a stream

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

@example 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

Methods

run  

Classes and Modules

Class Cheetah::DefaultRecorder
Class Cheetah::ExecutionFailed
Class Cheetah::NullRecorder
Class Cheetah::Recorder

Constants

VERSION = File.read(File.dirname(__FILE__) + "/../../VERSION").strip   Cheetah version (uses [semantic versioning](semver.org/)).
BUILTIN_DEFAULT_OPTIONS = { stdin: "", stdout: nil, stderr: nil, logger: nil, env: {}, chroot: "/"   @private
READ = 0
WRITE = 1

Attributes

default_options  [RW]  The default options of the {Cheetah.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 {Cheetah.run} documentation is used.

By default, no values are specified here.

@example 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 = {}

@return [Hash] the default options of the {Cheetah.run} method

Public Class methods

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 {Cheetah.default_options}. If a value is not specified there too, the default value described in the `options` parameter documentation is used.

@overload run(command, *args, options = {})

  Runs a command with its arguments specified separately.

  @param [String] command the command to execute
  @param [Array<String>] args the command arguments
  @param [Hash] options the options to execute the command with
  @option options [String, IO] :stdin ('') a `String` to use as command's
    standard input or an `IO` to read it from
  @option options [nil, :capture, IO] :stdout (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
  @option options [nil, :capture, IO] :stderr (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
  @option options [Logger, nil] :logger (nil) logger to log the command
    execution
  @option options [Recorder, nil] :recorder (DefaultRecorder.new) recorder
    to handle the command execution logging
  @option options [Fixnum, .include?, nil] :allowed_exitstatus (nil)
    Allows to specify allowed exit codes that do not cause exception. It
    adds as last element of result exitstatus.
  @option options [Hash] :env ({})
    Allows to update ENV for the time of running the command. if key maps to nil value it
    is deleted from ENV.
  @option options [String] :chroot ("/")
    Allows to run on different system root.

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

@overload 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.

  @param [Array<String>] command_and_args the command to execute (first
    element of the array) and its arguments (remaining elements)
  @param [Hash] options the options to execute the command with, same as
    in the first variant

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

@overload 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.

  @param [Array<Array<String>>] commands_and_args 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
  @param [Hash] options the options to execute the commands with, same as
    in the first variant

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

@raise [ExecutionFailed] when the execution fails

@example Run a command and capture its output

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

@example Run a command and capture its output into a stream

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

@example 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

@example Run a command with expected false and handle errors

  begin
    # exit code 1 for grep mean not found
    result = Cheetah.run("grep", "userA", "/etc/passwd", allowed_exitstatus: 1)
    if result == 0
      puts "found"
    else
      puts "not found"
    end
  rescue Cheetah::ExecutionFailed => e
    puts e.message
    puts "Standard output: #{e.stdout}"
    puts "Error ouptut:    #{e.stderr}"
  end

@example more complex example with allowed_exitstatus

  stdout, exitcode = Cheetah.run("cmd", stdout: :capture, allowed_exitstatus: 1..5)

[Validate]