Low commit activity in last 3 years
Run shell commands with captured stdout/stderr, exit code, duration measurement, configurable timeout, environment variables, line-by-line streaming, graceful signal escalation on timeout, and stdin piping.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

philiprehberger-task_runner

Tests Gem Version Last updated

Shell command runner with output capture, timeout, streaming, signal handling, and stdin piping

Requirements

  • Ruby >= 3.1

Installation

Add to your Gemfile:

gem "philiprehberger-task_runner"

Or install directly:

gem install philiprehberger-task_runner

Usage

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

Raising on Failure

# 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

Boolean Success Check

# Returns true for exit 0, false otherwise (timeouts also return false)
if Philiprehberger::TaskRunner.run?('which', 'git')
  puts 'git is installed'
end

Which

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.

Timeout

result = Philiprehberger::TaskRunner.run('long-process', timeout: 30)

Environment Variables and Working Directory

result = Philiprehberger::TaskRunner.run(
  'make', 'build',
  env: { 'DEBUG' => '1' },
  chdir: '/path/to/project'
)

Signal Handling

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)

Input Piping

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'))

Streaming Output

Philiprehberger::TaskRunner.run('tail', '-f', '/var/log/app.log', timeout: 10) do |line|
  puts ">> #{line}"
end

Stderr Streaming

Philiprehberger::TaskRunner.run('make', 'build') do |line, stream|
  case stream
  when :stdout then puts "OUT: #{line}"
  when :stderr then puts "ERR: #{line}"
  end
end

API

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)

Development

bundle install
bundle exec rspec
bundle exec rubocop

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT