Shell command runner with output capture, timeout, streaming, signal handling, and stdin piping
gem install philiprehberger-task_runnerShell command runner with output capture, timeout, streaming, signal handling, and stdin piping
Add to your Gemfile:
gem "philiprehberger-task_runner"
Or install directly:
gem install philiprehberger-task_runner
require "philiprehberger/task_runner"
result = Philiprehberger::TaskRunner.run('ls', '-la')
puts result.stdout
puts result.exit_code # => 0
puts result.success? # => true
puts result.duration # => 0.012
# Raises CommandError if the command exits non-zero
result = Philiprehberger::TaskRunner.run!('make', 'build')
# The error includes the full Result for inspection
begin
Philiprehberger::TaskRunner.run!('false')
rescue Philiprehberger::TaskRunner::CommandError => e
puts e.message # => "command exited with code 1"
puts e.result.stderr # => captured stderr
end
# Returns true for exit 0, false otherwise (timeouts also return false)
if Philiprehberger::TaskRunner.run?('which', 'git')
puts 'git is installed'
end
Locate an executable on PATH (like the which shell builtin). Returns the
absolute path or nil when not found:
Philiprehberger::TaskRunner.which("git")
# => "/usr/bin/git"
Philiprehberger::TaskRunner.which("definitely-not-installed")
# => nil
On Windows, candidate suffixes from ENV["PATHEXT"] (.COM, .EXE,
.BAT, .CMD) are tried automatically.
result = Philiprehberger::TaskRunner.run('long-process', timeout: 30)
result = Philiprehberger::TaskRunner.run(
'make', 'build',
env: { 'DEBUG' => '1' },
chdir: '/path/to/project'
)
result = Philiprehberger::TaskRunner.run(
'long-process',
timeout: 30,
signal: :TERM,
kill_after: 5
)
# On timeout: sends SIGTERM first, then SIGKILL after 5 seconds if still running
# result.signal reports which signal killed the process (:TERM, :KILL, or nil)
result = Philiprehberger::TaskRunner.run('cat', stdin: "hello world")
puts result.stdout # => "hello world"
# Also accepts IO objects
result = Philiprehberger::TaskRunner.run('wc', '-l', stdin: File.open('data.txt'))
Philiprehberger::TaskRunner.run('tail', '-f', '/var/log/app.log', timeout: 10) do |line|
puts ">> #{line}"
end
Philiprehberger::TaskRunner.run('make', 'build') do |line, stream|
case stream
when :stdout then puts "OUT: #{line}"
when :stderr then puts "ERR: #{line}"
end
end
| Method / Class | Description |
|---|---|
.run(cmd, *args, timeout:, env:, chdir:, signal:, kill_after:, stdin:) | Run a command and return a Result |
.run!(cmd, *args, **opts) | Same as run, raises CommandError on non-zero exit |
.run?(cmd, *args, **opts) | Boolean shortcut — true only when exit code is 0; timeouts return false |
.which(cmd) | Absolute path of cmd on PATH (or nil); honors PATHEXT on Windows |
CommandError#result | The failed Result object |
.run(cmd) { |line| ... } | Run with line-by-line stdout streaming |
.run(cmd) { |line, stream| ... } | Run with stdout and stderr streaming |
Result#stdout | Captured standard output |
Result#stderr | Captured standard error |
Result#exit_code | Process exit code |
Result#success? | Whether exit code is 0 |
Result#failure? | Logical inverse of #success? |
Result#duration | Execution time in seconds |
Result#signal | Signal that killed the process (:TERM, :KILL, or nil) |
Result#timed_out? | Whether the process was killed for exceeding its timeout |
Result#to_h | Hash representation of the result (includes :success and :timed_out) |
bundle install
bundle exec rspec
bundle exec rubocop
If you find this project useful: