Gem Version Gem Version Build Status License
Installation · Usage · Wiki · Examples · Contributing
cd "/log" do ls.each_line do |line| puts cat(line) end end
Yes, that's valid Ruby!
ls and cat are just shell commands, but RubyShell makes them behave like Ruby methods.
bundle add rubyshell
Or install directly:
gem install rubyshell
Ever written something like this?
# Bash: Find large files modified in the last 7 days, show top 10 with human sizes find . -type f -mtime -7 -exec ls -lh {} \; 2>/dev/null | \ awk '{print 5,ドル 9ドル}' | \ sort -hr | \ head -10
Or tried to do error handling in bash?
# Bash: Hope nothing goes wrong... output=$(some_command 2>&1) || echo "failed somehow"
sh do # Ruby + Shell: Same task, actually readable find(".", type: "f", mtime: "-7") .lines .map { |f| [File.size(f.strip), f.strip] } .sort_by(&:first) .last(10) .each { |size, file| puts "#{size / 1024}KB #{file}" } rescue RubyShell::CommandError => e puts "Failed: #{e.message}" puts "Exit code: #{e.status}" end
require 'rubyshell' sh do pwd # Run any command ls("-la") # With arguments mkdir("project") # Create directories docker("ps", all: true) # --all flag git("status", s: true) # -s flag end # Or chain directly sh.git("log", oneline: true, n: 5)
sh do # Using chain block chain { cat("access.log") | grep("ERROR") | wc("-l") } # Using bang pattern (cat!("data.csv") | sort! | uniq!).exec end
sh do cd "/var/log" do # Commands run here, then return to original dir tail("-n", "100", "syslog") end # Back to original directory end
sh do begin rm("-rf", "important_folder") rescue RubyShell::CommandError => e puts "Command: #{e.command}" puts "Stderr: #{e.stderr}" puts "Exit code: #{e.status}" end end
Run multiple commands concurrently and get results as they complete:
sh do results = parallel do curl("https://api1.example.com") curl("https://api2.example.com") chain { ls | wc("-l") } end results.each { |r| puts r } end
Returns an Enumerator with results in completion order. Errors are captured and returned as values (not raised).
# Command-level sh.npm("start", _env: { NODE_ENV: "production" }) # Block-level sh(env: { DATABASE_URL: "postgres://localhost/db" }) do rake("db:migrate") end # Global RubyShell.env[:API_KEY] = "secret" RubyShell.config(env: { DEBUG: "true" })
# Global RubyShell.debug = true # Block scope RubyShell.debug { sh.ls } # Per command sh.git("status", _debug: true) # Output: # Executed: git status # Duration: 0.003521s # Pid: 12345 # Exit code: 0 # Stdout: "On branch main..."
Parse command output directly into Ruby objects:
sh.cat("data.json", _parse: :json) # => Hash sh.cat("config.yml", _parse: :yaml) # => Hash sh.cat("users.csv", _parse: :csv) # => Array
# Debug mode for chains chain(debug: true) { ls | grep("test") } # Parse chain output chain(parse: :json) { curl("https://api.example.com") }
sh do # Stash changes, pull, pop, and show what changed changes = git("status", porcelain: true).lines if changes.any? puts "Stashing #{changes.count} changed files..." git("stash") git("pull", rebase: true) git("stash", "pop") else git("pull", rebase: true) end # Show recent commits by author git("log", oneline: true, n: 100) .lines .map { |line| `git show -s --format='%an' #{line.split.first}`.strip } .tally .sort_by { |_, count| -count } .first(5) .each { |author, count| puts "#{author}: #{count} commits" } end
sh do cd "/var/log" do # Parse nginx logs: top 10 IPs by request count cat("nginx/access.log") .lines .map { |line| line.split.first } # Extract IP .tally .sort_by { |_, count| -count } .first(10) .each { |ip, count| puts "#{ip.ljust(15)} #{count} requests" } end end
sh do # Remove containers that exited more than a day ago containers = docker("ps", a: true, format: "{{.ID}} {{.Status}}") .lines .select { |line| line.include?("Exited") } .map { |line| line.split.first } if containers.any? puts "Removing #{containers.count} dead containers..." docker("rm", *containers) end # Remove dangling images images = docker("images", f: "dangling=true", q: true).lines.map(&:strip) if images.any? puts "Removing #{images.count} dangling images..." docker("rmi", *images) end puts "Disk usage:" puts docker("system", "df") end
sh do # Convert all PNGs to WebP, preserving directory structure find(".", name: "*.png") .lines .map(&:strip) .each do |png| webp = png.sub(/\.png$/, ".webp") puts "Converting: #{png}" begin cwebp("-q", "80", png, o: webp) rm(png) rescue RubyShell::CommandError => e puts " Failed: #{e.message}" end end end
sh do puts "=== System Health ===" # Disk usage warnings df("-h") .lines .drop(1) .each do |line| parts = line.split usage = parts[4].to_i mount = parts[5] puts "WARNING: #{mount} at #{usage}%" if usage > 80 end # Memory info mem = cat("/proc/meminfo") .lines .first(3) .to_h { |l| k, v = l.split(":"); [k, v.strip] } puts "\nMemory: #{mem['MemAvailable']} available of #{mem['MemTotal']}" # Top 5 CPU consumers puts "\nTop CPU processes:" ps("aux", sort: "-%cpu") .lines .drop(1) .first(5) .each { |proc| puts " #{proc.split[10]}% - #{proc.split[10..-1].join(' ').slice(0, 40)}" } end
sh do files = find(".", name: "*.tmp", mtime: "+30").lines.map(&:strip) if files.empty? puts "No old temp files found." exit end puts "Found #{files.count} temp files older than 30 days:" files.first(10).each { |f| puts " #{f}" } puts " ... and #{files.count - 10} more" if files.count > 10 total_size = files.sum { |f| File.size(f) rescue 0 } puts "\nTotal size: #{total_size / 1024 / 1024}MB" print "\nDelete all? [y/N] " if gets.strip.downcase == 'y' files.each { |f| rm(f) } puts "Deleted #{files.count} files." end end
#!/usr/bin/env ruby require 'rubyshell' APP_NAME = "myapp" DEPLOY_PATH = "/var/www/#{APP_NAME}" sh do puts "Deploying #{APP_NAME}..." # Ensure clean state git("status", porcelain: true).lines.tap do |changes| abort "Uncommitted changes!" if changes.any? end # Run tests puts "Running tests..." rake("spec") # Build and deploy cd DEPLOY_PATH do git("pull", "origin", "main") bundle("install", deployment: true) rake("db:migrate") # Restart with zero downtime puts "Restarting..." systemctl("reload", APP_NAME) end puts "Deployed successfully!" rescue RubyShell::CommandError => e puts "Deploy failed: #{e.message}" exit 1 end
| Task | Bash | RubyShell |
|---|---|---|
| Error handling | cmd || echo "fail" |
rescue CommandError |
| String manipulation | echo $var | sed | awk |
result.gsub(/.../) |
| Data structures | Arrays only | Hashes, objects, classes |
| Iteration | for f in *; do |
.each, .map, .select |
| Testing | DIY | RSpec, Minitest |
See Wiki for complete documentation including all options and advanced features.
bin/setup # Install dependencies rake spec # Run tests rake rubocop # Lint code bin/console # Interactive console
Bug reports and pull requests are welcome on GitHub. See CONTRIBUTING.md for guidelines and testing patterns.
AvantsoftMIT License - see LICENSE.