command-exec -- execute shell commands with ease

Code Climate Build Status

Introduction

Description

The name of the library is command_exec. It's helps you running programs and check if the run was successful. It supports a vast amount of options you find in the one of the following sections usage.

Example:

require 'command_exec'

# command has to be in $PATH
command = CommandExec::Command.new( :echo , :parameter => 'hello world' )
command.run
p command.result

Target "Group"

If you need a library to execute programs which do a job and then terminate, command_exec is your friend.

If you need a library which supports error detection based on STDOUT, STDERR, RETURN CODE and/or LOG FILE command_exec is the right choice.

Limitations

The programs should NOT produce gigabytes of output (STDOUT, STDERR, LOG FILE) to search for errors.

Structure of documentation

Section Description
Introduction Metainformation
Usage How to use the library
Options Which options are available to parametrize the library
HowTo How to do ... with library
Further reading Other helpful information

Usage

Gem versioning

This gem uses semantic versioning. The major version is increased when breaking changes has been made. The minor version is increased if backward-compatiable changes introduce new functionality. The patch version is increased if a bug was fixed and the change is backward-compatible. Please see http://semver.org/ for more information.

Ruby Version

This gem supports ruby up from 1.9.3.

Install gem

Install the command_exec-gem via rubygems or whatever package manager (e.g. bundler) you like to use.

gem install command_exec

Include library

To include the library in your code, you could use this code snippet.

require 'command_exec'

Run command

There are two forms to execute a program. You could either use the long or the short form. In both cases a CommandExec::Command-object will be returned.

command = CommandExec::Command.new( :echo , :parameter => 'hello world' )
command.run
p command.result
command = CommandExec::Command.execute( :echo , :parameter => 'hello world' )
p command.result

Result of command execution

That result-object can be used to inspect the result of the command execution. It supports several different methods, but only some are from interest for external use. If you want a full list, please see the API-documentation at rdoc.info.

result = command.result

# which executable was run
result.executable

# !!content!! of log file
result.log_file

# pid unter which the command was run
result.pid

#if failed, why
result.reason_for_failure

#return code of command
result.return_code

#status of command execution
result.status

#content of stderr
result.stderr

#content of stdout
result.stdout

Serialize result of command execution

There are some methods which need a little more explanation. Those methods return a string representation of the result.

#return an array of lines
result.to_a

#return a hash
result.to_h

#serialize data to json
result.to_json

#serialize data to string
result.to_s

#serialize data to xml
result.to_xml

#serialize data to yaml
result.to_yaml

One can tell those methods which data should be returned. There are different fields available:

Field Symbol
Status :status
Return code :return_code
STDERR :stderr
STDOUT :stdout
Log file :log_file
Process identitfier (PID) :pid
Reason for failure reason_for_failure

Now, some small examples:

#result.<method>(field1,field2, ... , fieldn)
#result.<method>([field1,field2, ... , fieldn])

#all fields
result.to_a

#stderr and stdout only
result.to_a(:stderr, :stdout)

#stderr and stdout only (parameters given as a single array)
result.to_a([:stderr, :stdout])

Extended usage

There are multiple ways to tell command_exec about a command:

Search command in PATH

If the first parameter of run and execute is a Symbol the library will search for the command in the paths given in the $PATH-shell-variable.

command = CommandExec::Command.execute( :echo , 
                                        :parameter => 'hello world',
                                        )
p command.result

Path to command

If you prefer to use a full qualified path, this is possible as well.

command = CommandExec::Command.execute( '/bin/echo' , 
                                        :parameter => 'hello world',
                                        )
p command.result

It also supports relative paths. But be aware to tell the library the correct one. The base path for relative ones is the working directory of the library, not the working directory of the command (see section "Working directory" about that).

Dir.chdir('/tmp') do
  command = CommandExec::Command.execute( '../bin/echo' , 
                                          :parameter => 'hello world',
                                          :logger => Logger.new($stderr)
                                          )
  p command.result
end

Options

Logging

command_exec makes use of the Ruby Logger-class. If you would like to use another class/gem, nevermind, but it has to be compatible with the Logger-API.

To make it easier for you, command_exec provides a :logger option. It defaults to Logger.new($stderr).

command = CommandExec::Command.execute( :echo , 
                                        :parameter => 'hello world',
                                        :logger => Logger.new($stderr),
                                        )
p command.result

If you prefer more or less information you can make use of the :lib_log_level-option. With one exception, those log levels are the same like in the Logger-class. Additionally you can use :silent to suppress all output of the library, if you use the open3 and not the system runner. If you choose to use the system runner, STDOUT from the command won't be captured.

Option value Logger loglevel
:debug Logger::DEBUG
:info Logger::INFO
:warn Logger::WARN
:error Logger::ERROR
:fatal Logger::FATAL
:unknown Logger::UNKNOWN
:silent no output (log device is set to nil)
command = CommandExec::Command.execute( :echo , 
                                        :parameter => 'hello world' ,
                                        :lib_log_level => :debug,
                                        )
p command.result

Command options and parameter

The next two options (command options and command parameters) are very similar. Both will be used to build the command which should be executed. The main difference is the position of given string in the command string.

<command> <options> <parameter>

So, if you don't want to use the options- and/or the parameter-option, you don't need to do it. But may be there are situations, where you would like to be as concise as possible.

Recommended:

command = CommandExec::Command.execute( :ls , 
                                        :options => '-al',
                                        :parameter => '/bin',
                                        )
p command.result

But also valid:

command = CommandExec::Command.execute( :ls , 
                                        :options => '-al /bin',
                                        )
p command.result

Or:

command = CommandExec::Command.execute( :ls , 
                                        :parameter => '-al /bin',
                                        )
p command.result

Please check if you use single or double quotes correctly! command_exec takes the parameters and options as given. That's why

#will succeed
#see debug output for reason
command = CommandExec::Command.execute( :echo , 
                                        :options => '-e',
                                        :parameter => "\"Thats a string\n with a newline\"",
                                        :lib_log_level => :debug,
                                        )
p command.result

isn't the same like

#will fail
#see debug output for reason
command = CommandExec::Command.execute( :echo , 
                                        :options => '-e',
                                        :parameter => "Thats a string\n with a newline",
                                        :lib_log_level => :debug,
                                        )
p command.result

Command log file

If the command creates a log file, you can tell command_exec about that file via the :log_file-option. Honestly, this option only makes sense if you configure command_execto search for errors in the file (please see the chapter about Error detection for further information about that).

command = CommandExec::Command.execute( :ls , 
                                        :options => '-al',
                                        :log_file => '/path/to/log_file',
                                        )
p command.result

Command search path

If you need to change the paths where a command can be found, you could use the :search_path-option. It defaults to those paths found in $PATH.

It supports multiple values as Array:

command = CommandExec::Command.execute( :ls , 
                                        :options => '-al',
                                        :search_paths => [ '/bin' ],
                                        )
p command.result

Or single values as String:

command = CommandExec::Command.execute( :ls , 
                                        :options => '-al',
                                        :search_paths => '/bin',
                                        )
p command.result

Error detection

command_exec is capable of searching for errors. To enable error detection you need to activate it via the :error_detection_on-option. It supports error detection on:

Search in... Symbol
Return code :return_code
STDOUT :stdout
STDERR :stderr
Log file :log_file

But you need to provide information, what item indicates an error.

Indicator for... Options Type
Return code :allowed_return_code
:forbidden_return_code
Array
STDERR :allowed_words_in_stderr
:forbidden_words_in_stderr
Array
STDOUT :allowed_words_in_stdout
:forbidden_words_in_stdout
Array
Log file :allowed_words_in_log_file
:forbidden_words_in_log_file
Array

Return code

If the command returns helpful return codes, those can be used to check if an error occured. You can tell command_exec about allowed or forbidden return codes.

#All error codes except `0` will be detected as an error.
command = CommandExec::Command.execute( :false , 
                                        :error_detection_on => [:return_code],
                                        :error_indicators => {
                                          :allowed_return_code => [0],
                                        },
                                        )
p command.result

#If the command exits with a return code of `1`, this will be detected as an
#error.
command = CommandExec::Command.execute( :false , 
                                        :error_detection_on => [:return_code],
                                        :error_indicators => {
                                          :forbidden_return_code => [1],
                                        },
                                        )
p command.result

In the case of the detection of errors command_execdefaults to:

:error_detection_on => [:return_code],
:allowed_return_code => [0],

STDOUT

command_exec can search for errors in STDOUT. To enable this functionality, you need to set the :error_detection_on-option on ':stdout'. Furthermore you need to tell the library, what strings are error indicators (forbidden_words_in_stdout). If there are some strings which contain the error string(s), but are no errors, you need to use the allowed_words_in_stdout-option. The same is true, if the allowed word is in the same line.

#Simple error search
#will fail
command = CommandExec::Command.execute( :echo , 
                                        :options => '-e',
                                        :parameter => "\"wow, a test. That's great.\nBut an error occured in this line\"",
                                        :error_detection_on => [:stdout],
                                        :error_indicators => {
                                          :forbidden_words_in_stdout => %w{ error }
                                        },
                                        )
p command.result

#error indicator in string, which is no error
#will succeed
command = CommandExec::Command.execute( :echo , 
                                        :options => '-e',
                                        :parameter => "\"wow, a test. That's great.\nBut no error occured in this line\"",
                                        :error_detection_on => [:stdout],
                                        :error_indicators => {
                                          :forbidden_words_in_stdout => %w{ error },
                                          :allowed_words_in_stdout => ["no error occured"] , 
                                        },
                                        )
p command.result

#error indicator in same line, which is no error
#will succeed
command = CommandExec::Command.execute( :echo , 
                                        :options => '-e',
                                        :parameter => "\"wow, a test. That's great.\nBut no error occured in this line because of some other string\"",
                                        :error_detection_on => [:stdout],
                                        :error_indicators => {
                                          :forbidden_words_in_stdout => %w{ error },
                                          :allowed_words_in_stdout => ["some other string"] , 
                                        },
                                        )
p command.result

STDERR

The same is true for STDERR. You need to activate the error detection via :error_detection_on => [:stderr]. The error indicators can be given via :forbidden_words_in_stderr => %w{ error } and :allowed_words_in_stdout => ["some other string"].

#will fail
command = CommandExec::Command.execute( :echo , 
                                        :options => '-e',
                                        :parameter => "\"wow, a test. That's great.\nBut an error occured in this line\" >&2",
                                        :error_detection_on => [:stderr],
                                        :error_indicators => {
                                          :forbidden_words_in_stderr => %w{ error },
                                        },
                                        )
p command.result

LOG FILE

To search for errors in the log file a command created during execution, you need to provide the information where command_exec finds the log file (see section Command Log file).

The options are very similar to those for STDERR and STDOUT: To activate error detection for log files use :error_detection_on => [:log_file]. The error indicators can be given via :forbidden_words_in_log_file => %w{ error } and :allowed_words_in_log_file => ["some other string"].

File.open('/tmp/test.log', 'w') do |f|
  f.write "wow, a test. That's great.\nBut an error occured in this line"
end

#will fail
command = CommandExec::Command.execute( :echo , 
                                        :error_detection_on => [:log_file],
                                        :log_file => '/tmp/test.log',
                                        :error_indicators => {
                                          :forbidden_words_in_log_file => %w{ error },
                                        },
                                        )
p command.result

Working directory

To change the working directory for the command you can use the :working_directory-option.

command = CommandExec::Command.execute( :ls , 
                                        :options => '-al',
                                        :working_directory => '/tmp',
                                        )
p command.result

Error reaction

If an error occured, command_exec can raise an exception, 'throw' an error or do nothing at all. Besides the configured option, on every run it returns the result for the run (see Result of command execution for more details).

Raise an exception aka error

If an error occured during command execution, you can tell command_exec to raise an exception.

begin
  command = CommandExec::Command.execute( :false , 
                                          :on_error_do => :raise_error,
                                        )
rescue CommandExec::Exceptions::CommandExecutionFailed => e
  puts e.message
end

Throw error

If you prefer not to use execptions, you can use ruby's throw-catch-mechanism.

catch :command_execution_failed do 
  command = CommandExec::Command.execute( :false , 
                                          :on_error_do => :throw_error,
                                        )
end

Runner

Today there are two runners available: :open3 and system. Use the first one if you want :stdout and :stderr to be captured and searched for errors. If you're only interested in the :return_code you could use the :system-runner. Please be aware, that using the system-runner + error detection on stdout, stderr is not working as you might expect.

#will fail
command = CommandExec::Command.execute( :echo , 
                                        :options => '-e',
                                        :parameter => "\"wow, a test. That's great.\nBut an error occured in this line\"",
                                        :error_detection_on => [:stdout],
                                        :error_indicators => {
                                          :forbidden_words_in_stdout => %w{ error }
                                        },
                                        :run_via => :open3,
                                        )
p command.result

#will succeed, because stdout was not caputured
command = CommandExec::Command.execute( :echo , 
                                        :options => '-e',
                                        :parameter => "\"wow, a test. That's great.\nBut an error occured in this line\"",
                                        :error_detection_on => [:stdout],
                                        :error_indicators => {
                                          :forbidden_words_in_stdout => %w{ error }
                                        },
                                        :run_via => :system,
                                        )
p command.result

HowTo

TBD

Further Reading

Dependencies

Please see the gemspec for runtime dependencies and the 'Gemfile' for development dependencies.

Todo

Please see TODO.md for enhancements which are planned for implementation.

Development

  1. Fork it
  2. Create your remote (git remote add <your_remote_repo> <path_to_repo>)
  3. Create your feature branch (git checkout -b my-new-feature)
  4. Commit your changes (git commit -am 'Added some feature')
  5. Push to the branch (git push <your_remote_repo> my-new-feature)
  6. Create new Pull Request

The API-documentation can be found at http://rdoc.info/github/maxmeyer/command_exec/frames

Please see 'http://git-scm.com/book' first if you have further questions about git.

Copyright

(c) 2012-, Max Meyer

License

Please see LICENSE.md for license text.