cyberangles guide

Creating Interactive Ruby Scripts for Automation

Automation scripts are powerful tools for streamlining repetitive tasks, but static scripts often lack flexibility. What if you could build scripts that **adapt to user input**, ask questions, and guide users through workflows in real time? That’s where interactive Ruby scripts shine. Ruby, with its readability, rich standard library, and vibrant gem ecosystem, is ideal for creating interactive automation tools. Whether you’re building a system maintenance helper, a deployment assistant, or a custom CLI tool, interactive scripts make automation accessible—even for users who aren’t comfortable editing code. In this guide, we’ll explore how to design, build, and enhance interactive Ruby scripts. From handling user input to integrating system commands, adding error handling, and even building menus, you’ll learn everything you need to create scripts that are both functional and user-friendly.

Table of Contents

Prerequisites

Before diving in, ensure you have:

  • Ruby installed (version 2.7+ recommended; use ruby -v to check).
  • Basic Ruby knowledge (variables, loops, conditionals, methods).
  • A text editor (e.g., VS Code, Sublime Text) or IDE.
  • Familiarity with the command line (to run scripts and install gems).

Core Concepts: Input, Output, and Flow Control

At the heart of any interactive script are three pillars: capturing user input, providing clear output, and controlling the workflow based on input. Let’s break these down.

Capturing User Input

Ruby’s Kernel#gets method reads input from the user (via standard input, STDIN). By default, gets includes the newline character (\n), so we use chomp to remove it:

puts "What's your name?"
name = gets.chomp  # Waits for user input, then removes the newline
puts "Hello, #{name}!"

Key Notes:

  • gets pauses execution until the user presses Enter.
  • For sensitive input (e.g., passwords), use STDIN.noecho(&:gets).chomp to hide input:
    puts "Enter password:"
    password = STDIN.noecho(&:gets).chomp  # Input won't appear on screen

Providing Clear Output

Use puts (prints with a newline) or print (no newline) for basic output. For richer formatting, consider gems like colorize (adds color) or tty-progressbar (for progress bars).

Example with colorize (install first: gem install colorize):

require 'colorize'

puts "Success!".green
puts "Warning: Low disk space".yellow
puts "Error: File not found".red

Controlling Workflow with Loops and Conditionals

Interactive scripts often need to run until the user chooses to exit. Use loop do with a break condition, or while/until loops.

Example: A simple loop that greets users until they type “exit”:

loop do
  puts "Enter your name (or 'exit' to quit):"
  input = gets.chomp.downcase

  if input == "exit"
    puts "Goodbye!"
    break  # Exit the loop
  else
    puts "Hello, #{input.capitalize}!"
  end
end

Enhancing Interactivity: Menus, Prompts, and Validation

Basic input/output works for simple scripts, but for complex workflows, you’ll want menus, smart prompts, and validation to guide users.

Building Interactive Menus

Menus let users select options from a list (e.g., “1. Backup Files”, “2. Clean Temp”). Use a loop to redisplay the menu until the user chooses “Exit”.

Example: A numbered menu system:

def display_menu
  puts "\n=== System Maintenance Helper ===".blue
  puts "1. Check disk space"
  puts "2. Clean temp files"
  puts "3. Exit"
  print "Enter your choice (1-3): "
end

loop do
  display_menu
  choice = gets.chomp.to_i  # Convert input to integer

  case choice
  when 1
    puts "Checking disk space..."  # We'll add logic later!
  when 2
    puts "Cleaning temp files..."
  when 3
    puts "Exiting. Goodbye!"
    break
  else
    puts "Invalid choice. Please enter 1-3.".red
  end
end

Input Validation

Prevent errors by validating user input (e.g., ensuring a number is entered when expected). Use conditionals, regular expressions, or Ruby’s type conversion methods with rescue.

Example: Validating numeric input:

def get_valid_number(prompt)
  loop do
    print prompt
    input = gets.chomp

    # Try converting to integer; rescue if it fails
    begin
      number = Integer(input)
      return number  # Exit loop if valid
    rescue ArgumentError
      puts "Invalid input. Please enter a number.".red
    end
  end
end

age = get_valid_number("Enter your age: ")
puts "You are #{age} years old."

Example: Validating with regular expressions (e.g., email):

def valid_email?(email)
  /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i.match?(email)
end

loop do
  print "Enter your email: "
  email = gets.chomp
  if valid_email?(email)
    puts "Valid email: #{email}"
    break
  else
    puts "Invalid email format. Try again.".red
  end
end

Fancy Prompts with Gems (e.g., tty-prompt)

For polished UIs, use gems like tty-prompt (install: gem install tty-prompt). It provides select menus, yes/no prompts, date pickers, and more—no manual validation needed!

Example with tty-prompt:

require 'tty-prompt'

prompt = TTY::Prompt.new

# Select menu
choice = prompt.select("What would you like to do?", %w[Backup Clean Exit])
puts "You chose: #{choice}"

# Yes/no prompt
confirm = prompt.yes?("Are you sure you want to delete temp files?")
if confirm
  puts "Deleting files..."
else
  puts "Aborted."
end

# Text input with validation
email = prompt.ask("Enter your email:", validate: { "Invalid email" => ->(e) { valid_email?(e) } })

Integrating System Tools and Commands

Interactive scripts often need to run system commands (e.g., df -h to check disk space, rm -rf /tmp/* to clean temp files). Ruby makes this easy with:

  • Backticks (`): Capture command output as a string.
  • system(): Run a command and return true/false for success/failure.
  • Open3: Capture stdout, stderr, and exit status (more control).

Example: Run df -h and display disk space

# Using backticks to capture output
disk_space = `df -h`
puts "Disk Space:\n#{disk_space}"

# Using Open3 to capture errors (install: `gem install open3` if needed)
require 'open3'

stdout, stderr, status = Open3.capture3('df -h')
if status.success?
  puts "Disk Space:\n#{stdout}"
else
  puts "Error: #{stderr}".red
end

Example: Clean temp files with confirmation

require 'tty-prompt'

prompt = TTY::Prompt.new

if prompt.yes?("Clean /tmp directory? This will delete all files!")
  stdout, stderr, status = Open3.capture3('rm -rf /tmp/*')
  if status.success?
    puts "Temp files cleaned!".green
  else
    puts "Cleanup failed: #{stderr}".red
  end
else
  puts "Cleanup canceled.".yellow
end

Error Handling

Even with validation, scripts can fail (e.g., a system command throws an error). Use begin/rescue blocks to catch exceptions and guide users.

Example: Handling file read errors:

begin
  file = File.open("config.txt", "r")
  content = file.read
  puts "Config loaded:\n#{content}"
rescue Errno::ENOENT  # "No such file or directory" error
  puts "Error: config.txt not found.".red
  puts "Please create the file and try again."
rescue Errno::EACCES  # Permission denied
  puts "Error: Permission denied to read config.txt.".red
ensure
  file&.close  # Close the file if it was opened (safe with &. "safe navigation")
end

Testing Interactive Scripts

Testing interactive scripts can be tricky because they rely on user input. Use tools like:

  • minitest/rspec: For unit testing non-interactive logic.
  • mocha: Stub gets to simulate user input.

Example: Test a menu choice with minitest and mocha:

require 'minitest/autorun'
require 'mocha/minitest'

def process_choice(choice)
  case choice
  when 1 then "backup"
  when 2 then "clean"
  else "invalid"
  end
end

class TestScript < Minitest::Test
  def test_process_choice
    assert_equal "backup", process_choice(1)
    assert_equal "clean", process_choice(2)
    assert_equal "invalid", process_choice(99)
  end
end

# Test interactive input by stubbing `gets`
def test_menu_loop
  Kernel.stubs(:gets).returns("1\n", "3\n")  # Simulate user entering "1" then "3"
  # Add assertions to verify output
end

Advanced Techniques

Using Config Files for Persistence

Store settings (e.g., backup directories, API keys) in config files (YAML/JSON) instead of hardcoding. Use Ruby’s yaml library to load/save configs.

Example with YAML (install: gem install yaml):

require 'yaml'

# Load config
config = YAML.load_file('config.yaml') rescue { "backup_dir" => "/default/backup" }

# Use config
puts "Backup directory: #{config['backup_dir']}"

# Save config
config['backup_dir'] = "/new/backup"
File.write('config.yaml', config.to_yaml)

Adding CLI Arguments with OptionParser

Let power users skip interactivity by passing arguments (e.g., ./script.rb --clean). Use Ruby’s built-in OptionParser for this.

Example:

require 'optparse'

options = { clean: false, backup: false }

parser = OptionParser.new do |opts|
  opts.banner = "Usage: script.rb [options]"

  opts.on("-c", "--clean", "Clean temp files") { options[:clean] = true }
  opts.on("-b", "--backup", "Run backup") { options[:backup] = true }
  opts.on("-h", "--help", "Show this help") { puts opts; exit }
end

parser.parse!

# Run commands based on options
if options[:clean]
  puts "Cleaning temp files..."
elsif options[:backup]
  puts "Running backup..."
else
  puts "No options provided. Starting interactive mode..."
  # Launch interactive menu here
end

Example Project: Build a “System Maintenance Helper”

Let’s tie it all together with a script that:

  1. Shows a menu for disk space, cleaning temp files, or exiting.
  2. Uses tty-prompt for user-friendly input.
  3. Runs system commands (df -h, rm -rf /tmp/*).
  4. Handles errors with Open3.

Step 1: Set Up Dependencies

gem install tty-prompt colorize open3

Step 2: Write the Script (maintenance_helper.rb)

require 'tty-prompt'
require 'colorize'
require 'open3'

prompt = TTY::Prompt.new

def check_disk_space
  puts "\nChecking disk space...\n".blue
  stdout, stderr, status = Open3.capture3('df -h')
  if status.success?
    puts stdout
  else
    puts "Error checking disk space: #{stderr}".red
  end
end

def clean_temp_files
  puts "\nCleaning /tmp directory...\n".blue
  if prompt.yes?("This will delete ALL files in /tmp! Continue?")
    stdout, stderr, status = Open3.capture3('rm -rf /tmp/*')
    if status.success?
      puts "Temp files cleaned successfully!".green
    else
      puts "Cleanup failed: #{stderr}".red
    end
  else
    puts "Cleanup canceled.".yellow
  end
end

loop do
  system('clear') || system('cls')  # Clear terminal (cross-platform)
  choice = prompt.select("=== System Maintenance Helper ===".green, [
    { name: "Check Disk Space", value: :disk },
    { name: "Clean Temp Files", value: :clean },
    { name: "Exit", value: :exit }
  ])

  case choice
  when :disk then check_disk_space
  when :clean then clean_temp_files
  when :exit
    puts "Goodbye!".blue
    break
  end

  prompt.keypress("Press any key to continue...")
end

Step 3: Run the Script

ruby maintenance_helper.rb

You’ll see a clean menu, get prompted for confirmation, and see colored output—all while integrating system commands!

Conclusion

Interactive Ruby scripts transform rigid automation into flexible, user-friendly tools. By combining input handling, menus, system commands, and error handling, you can build scripts that adapt to user needs.

Start small (e.g., a backup script with a menu), then layer in gems like tty-prompt for polish. With Ruby’s simplicity and power, the possibilities are endless—happy scripting!

References