# Automation, Rake and Command-Line Tools — Ruby

Source: https://www.geekswithgeeks.com/en/ruby/p-scripts

> Write maintainable scripts, Rake tasks and command-line tools in Ruby.

## Ruby as a glue language

Ruby is a pleasant language for **automation**: renaming files, generating reports, calling APIs, migrating data or wrapping shell commands. Keep scripts maintainable: put logic in methods or small classes, read configuration from `ENV` and arguments, exit with meaningful status codes (`exit 1` or `abort "message"`), and print errors to `$stderr`. Parse options with the standard **`OptionParser`** or gems such as **Thor**. Run shell commands safely with **`Open3.capture3("cmd", arg1, arg2)`**, passing arguments separately so they are not interpreted by a shell, rather than backticks with interpolated user input. **Rake** (Ruby make) defines tasks with dependencies in a `Rakefile`: `task :report => :fetch do ... end`, run with `rake report`; Rails uses Rake for many maintenance tasks. For distributable tools, package them as gems with an executable in `exe/`. Scripts that will run in CI or cron should be idempotent and log what they do.

## A small CLI with OptionParser and Open3

Options, safe subprocess calls and proper exit codes.

```ruby
#!/usr/bin/env ruby
# frozen_string_literal: true

require "optparse"
require "open3"

options = { days: 7, dry_run: false }
OptionParser.new do |opts|
  opts.banner = "Usage: cleanup_logs [options] DIR"
  opts.on("-d", "--days N", Integer, "Delete logs older than N days") { options[:days] = _1 }
  opts.on("-n", "--dry-run", "Only print what would be deleted") { options[:dry_run] = true }
end.parse!

dir = ARGV.first or abort("error: DIR is required")
abort("error: #{dir} is not a directory") unless File.directory?(dir)

cutoff = Time.now - (options[:days] * 86_400)
old_logs = Dir.glob(File.join(dir, "*.log")).select { File.mtime(_1) < cutoff }

old_logs.each do |path|
  if options[:dry_run]
    puts "would delete #{path}"
  else
    File.delete(path)
  end
end

stdout, stderr, status = Open3.capture3("df", "-h", dir)   # arguments passed separately, no shell
$stderr.puts(stderr) unless status.success?
puts stdout
exit(status.success? ? 0 : 1)

# Rakefile:
# task :cleanup do
#   sh "ruby", "bin/cleanup_logs", "--days", "14", "log"
# end
```

## A versatile kitchen helper

Ruby scripts are the kitchen helper who can chop, measure, label and tidy up between the big appliances. Rake is the list on the fridge saying which jobs depend on which.

**Quiz:** Why prefer Open3.capture3("cmd", arg) over a backtick command with interpolated input?

- [ ] It is shorter
- [x] Arguments are passed directly without a shell, preventing command injection, and you get stdout, stderr and status
- [ ] Backticks do not work in Ruby
- [ ] It runs faster on every system

*Answer:* Arguments are passed directly without a shell, preventing command injection, and you get stdout, stderr and status. Passing arguments separately avoids shell interpretation of untrusted input.
