Table of Contents
- Prerequisites
- Core Concepts: Input, Output, and Flow Control
- Enhancing Interactivity: Menus, Prompts, and Validation
- Integrating System Tools and Commands
- Error Handling: Making Scripts Robust
- Testing Interactive Scripts
- Advanced Techniques
- Example Project: Build a System Maintenance Helper
- Conclusion
- References
Prerequisites
Before diving in, ensure you have:
- Ruby installed (version 2.7+ recommended; use
ruby -vto 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:
getspauses execution until the user presses Enter.- For sensitive input (e.g., passwords), use
STDIN.noecho(&:gets).chompto 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 returntrue/falsefor 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: Stubgetsto 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:
- Shows a menu for disk space, cleaning temp files, or exiting.
- Uses
tty-promptfor user-friendly input. - Runs system commands (
df -h,rm -rf /tmp/*). - 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!