Cheetah
Your swiss army knife for executing external commands in Ruby safely and conveniently.
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 output:    #{e.stderr}"
endFeatures
- 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) but allows to specify which non-zero codes are not an error
- Thread-safety
- Allows overriding environment variables
- Optional logging for easy debugging
- Running on changed root ( requires chroot permission )
Non-features
- Handling of interactive commands
Installation
$ gem install cheetah
Usage
First, require the library:
require "cheetah"You can now use the Cheetah.run method to run commands.
Running Commands
To run a command, just specify it together with its arguments:
Cheetah.run("tar", "xzf", "foo.tar.gz")
Cheetah converts each argument to a string using `#to_s`.Passing Input
Using the :stdin option you can pass a string to command's standard input:
Cheetah.run("python", stdin: source_code)If the input is big you may want to avoid passing it in one huge string. In that
case, pass an IO as a value of the :stdin option. The command will read its
input from it gradually.
File.open("huge_program.py") do |stdin|
  Cheetah.run("python", stdin: stdin)
endCapturing Output
To capture command's standard output, set the :stdout option to :capture.
You will receive the output as a return value of the call:
files = Cheetah.run("ls", "-la", stdout: :capture)The same technique works with the error output — just use the :stderr option.
If you specify capturing of both outputs, the return value will be a two-element
array:
results, errors = Cheetah.run("grep", "-r", "User", ".", stdout: => :capture, stderr: => :capture)If the output is big you may want to avoid capturing it into a huge string. In
that case, pass an IO as a value of the :stdout or :stderr option. The
command will write its output into it gradually.
File.open("files.txt", "w") do |stdout|
  Cheetah.run("ls", "-la", stdout: stdout)
endPiping Commands
You can pipe multiple commands together and execute them as one. Just specify the commands together with their arguments as arrays:
processes = Cheetah.run(["ps", "aux"], ["grep", "ruby"], stdout: :capture)Error Handling
If the command can't be executed for some reason or returns an unexpected non-zero exit status, Cheetah raises an exception with detailed information about the failure:
# 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 output:    #{e.stderr}"
  puts "Exit status:     #{e.status.exitstatus}"
endLogging
For debugging purposes, you can use a logger. Cheetah will log the command, its status, input and both outputs to it:
Cheetah.run("ls -l", logger: logger)Overwriting env
If the command needs adapted environment variables, use the :env option. Passed hash is used to update existing env (for details see ENV.update). Nil value means unset variable. Environment is restored to its original state after running the command.
  Cheetah.run("env", env: { "LC_ALL" => "C" })Expecting Non-zero Exit Status
If command is expected to return valid a non-zero exit status like grep command
which return 1 if given regexp is not found, then option :allowed_exitstatus
can be used:
# Run a command, handle exitstatus  and handle errors
begin
  exitstatus = Cheetah.run("grep", "userA", "/etc/passwd", allowed_exitstatus: 1)
  if exitstates == 0
    puts "found"
  else
    puts "not found"
  end
rescue Cheetah::ExecutionFailed => e
  puts e.message
  puts "Standard output: #{e.stdout}"
  puts "Error output:    #{e.stderr}"
  puts "Exit status:     #{e.status.exitstatus}"
endExit status is returned as last element of result. If it is only captured thing,
then it is return without array.
Supported input for allowed_exitstatus are anything supporting include, fixnum
or nil for no allowed existatus.
# allowed inputs
allowed_exitstatus: 1
allowed_exitstatus: 1..5
allowed_exitstatus: [1, 2]
allowed_exitstatus: object_with_include_method
allowed_exitstatus: nilSetting Defaults
To avoid repetition, you can set global default value of any option passed too
Cheetah.run:
# If you're tired of passing the :logger option all the time...
Cheetah.default_options = { :logger => my_logger }
Cheetah.run("./configure")
Cheetah.run("make")
Cheetah.run("make", "install")
Cheetah.default_options = {}Changing Working Directory
If diferent working directory is needed for running program, then suggested
usage is to enclose call into Dir.chdir method.
Dir.chdir("/workspace") do
  Cheetah.run("make")
endChanging System Root
If a command needs to be executed in different system root then the :chroot
option can be used:
Cheetah.run("/usr/bin/inspect", chroot: "/mnt/target_system")More Information
For more information, see the API documentation.
Compatibility
Cheetah should run well on any Unix system with Ruby 2.0.0, 2.1 and 2.2. Non-Unix systems and different Ruby implementations/versions may work too but they were not tested.