Module with various helper methods for executing external commands. In most cases, you can use sh
to run commands and have decent logging done. You will likely use this in a class that also mixes-in OptparsePlus::CLILogging
(remembering that OptparsePlus::Main
mixes this in for you).
If you don't, you must provide a logger via set_sh_logger
.
Examples
include OptparsePlus::SH
sh 'cp foo.txt /tmp'
# => logs the command to DEBUG, executes the command, logs its output to DEBUG and its
# error output to WARN, returns 0
sh 'cp non_existent_file.txt /nowhere_good'
# => logs the command to DEBUG, executes the command, logs its output to INFO and
# its error output to WARN, returns the nonzero exit status of the underlying command
sh! 'cp non_existent_file.txt /nowhere_good'
# => same as above, EXCEPT, raises a OptparsePlus::FailedCommandError
sh 'cp foo.txt /tmp' do
# Behaves exactly as before, but this block is called after
end
sh 'cp non_existent_file.txt /nowhere_good' do
# This block isn't called, since the command failed
end
sh 'ls -l /tmp/' do |stdout|
# stdout contains the output of the command
end
sh 'ls -l /tmp/ /non_existent_dir' do |stdout,stderr|
# stdout contains the output of the command,
# stderr contains the standard error output.
end
Handling process execution
In order to work on as many Rubies as possible, this class defers the actual execution to an execution strategy. See set_execution_strategy
if you think you'd like to override that, or just want to know how it works.
More complex execution and subprocess management
This is not intended to be a complete replacement for Open3 or an enhanced means of managing subprocesses. This is to make it easy for you to shell-out to external commands and have your app be robust and easy to maintain.
Source: show
# File lib/optparse_plus/sh.rb, line 66 def self.included(k) k.extend(self) end
Set the strategy to use for executing commands. In general, you don't need to set this since this module chooses an appropriate implementation based on your Ruby platform:
- 1.8 Rubies, including 1.8, and REE
-
Open4 is used via
OptparsePlus::ExecutionStrategy::Open_4
.open4
will not be installed as a dependency. RubyGems doesn't allow conditional dependencies, so make sure that your app declares it as a dependency if you think you'll be running on 1.8 or REE. - Rubinius
-
Open4 is used, but we handle things a bit differently; see
OptparsePlus::ExecutionStrategy::RBXOpen_4
. Same warning on dependencies applies. - JRuby
-
Use JVM calls to
Runtime
viaOptparsePlus::ExecutionStrategy::JVM
- Windows
-
Currently no support for Windows
- All others
-
we use Open3 from the standard library, via
OptparsePlus::ExecutionStrategy::Open_3
See OptparsePlus::ExecutionStrategy::Base
for how to implement your own.
Source: show
# File lib/optparse_plus/sh.rb, line 172 def set_execution_strategy(strategy) @execution_strategy = strategy end
Override the default logger (which is the one provided by CLILogging
). You would do this if you want a custom logger or you aren't mixing-in CLILogging
.
Note that this method is not called sh_logger=
to avoid annoying situations where Ruby thinks you are setting a local variable
Source: show
# File lib/optparse_plus/sh.rb, line 154 def set_sh_logger(logger) @sh_logger = logger end
Run a shell command, capturing and logging its output. If the command completed successfully, it's output is logged at DEBUG. If not, its output as logged at INFO. In either case, its error output is logged at WARN.
- command
-
the command to run as a String or Array of String. The String form is simplest, but is open to injection. If you need to execute a command that is assembled from some portion of user input, consider using an Array of String. This form prevents tokenization that occurs in the String form. The first element is the command to execute, and the remainder are the arguments. See
OptparsePlus::ExecutionStrategy::Base
for more info. - options
-
options to control the call. Currently responds to:
:expected
-
an Int or Array of Int representing error codes, in addition to 0, that are expected and therefore constitute success. Useful for commands that don't use exit codes the way you'd like
- block
-
if provided, will be called if the command exited nonzero. The block may take 0, 1, 2, or 3 arguments. The arguments provided are the standard output as a string, standard error as a string, and the exitstatus as an Int. You should be safe to pass in a lambda instead of a block, as long as your lambda doesn't take more than three arguments
Example
sh "cp foo /tmp"
sh "ls /tmp" do |stdout|
# stdout contains the output of ls /tmp
end
sh "ls -l /tmp foobar" do |stdout,stderr|
# ...
end
Returns the exit status of the command. Note that if the command doesn't exist, this returns 127.
Source: show
# File lib/optparse_plus/sh.rb, line 100 def sh(command,options={},&block) sh_logger.debug("Executing '#{command}'") stdout,stderr,status = execution_strategy.run_command(command) process_status = OptparsePlus::ProcessStatus.new(status,options[:expected]) sh_logger.warn("stderr output of '#{command}': #{stderr}") unless stderr.strip.length == 0 if process_status.success? sh_logger.debug("stdout output of '#{command}': #{stdout}") unless stdout.strip.length == 0 call_block(block,stdout,stderr,process_status.exitstatus) unless block.nil? else sh_logger.info("stdout output of '#{command}': #{stdout}") unless stdout.strip.length == 0 sh_logger.warn("Error running '#{command}'") end process_status.exitstatus rescue *exception_meaning_command_not_found => ex sh_logger.error("Error running '#{command}': #{ex.message}") 127 end
Run a command, throwing an exception if the command exited nonzero. Otherwise, behaves exactly like sh
.
- options
-
options hash, responding to:
:expected
-
same as for
sh
:on_fail
-
a custom error message. This allows you to have your app exit on shell command failures, but customize the error message that they see.
Raises OptparsePlus::FailedCommandError
if the command exited nonzero.
Examples:
sh!("rsync foo bar")
# => if command fails, app exits and user sees: "error: Command 'rsync foo bar' exited 12"
sh!("rsync foo bar", :on_fail => "Couldn't rsync, check log for details")
# => if command fails, app exits and user sees: "error: Couldn't rsync, check log for details
Source: show
# File lib/optparse_plus/sh.rb, line 139 def sh!(command,options={},&block) sh(command,options,&block).tap do |exitstatus| process_status = OptparsePlus::ProcessStatus.new(exitstatus,options[:expected]) unless process_status.success? raise OptparsePlus::FailedCommandError.new(exitstatus,command,options[:on_fail]) end end end