Overview
OptionParser provides Ruby applications with command-line argument processing capabilities. The library transforms command-line arguments into structured data, handles option validation, and generates help text. OptionParser supports short and long option formats, required and optional arguments, and automatic type conversion.
The primary class OptionParser
acts as a parser builder and processor. Applications define options using the #on
method, which associates option patterns with processing blocks. The parser processes ARGV or custom argument arrays, executing the appropriate blocks for matched options.
require 'optparse'
options = {}
parser = OptionParser.new do |opts|
opts.banner = "Usage: myapp [options]"
opts.on("-v", "--verbose", "Run verbosely") do |v|
options[:verbose] = v
end
end
parser.parse!(ARGV)
puts "Verbose mode: #{options[:verbose]}"
OptionParser handles three primary option types: flags (boolean switches), options with required arguments, and options with optional arguments. The library automatically converts arguments to appropriate Ruby types based on the option definition.
parser = OptionParser.new do |opts|
opts.on("-p", "--port PORT", Integer, "Port number") do |port|
options[:port] = port
end
opts.on("-f", "--file [FILE]", "Optional file") do |file|
options[:file] = file || "default.txt"
end
end
The parser distinguishes between destructive and non-destructive parsing methods. #parse!
modifies the original argument array by removing processed options, while #parse
returns a new array with unprocessed arguments.
Basic Usage
OptionParser construction begins with creating a parser instance and defining options through the #on
method. Each option definition specifies the option format, argument requirements, and processing behavior.
require 'optparse'
options = {}
OptionParser.new do |parser|
parser.banner = "Usage: backup.rb [options] directory"
parser.on("-c", "--compress", "Enable compression") do
options[:compress] = true
end
parser.on("-o", "--output FILE", "Output file") do |file|
options[:output] = file
end
parser.on("-l", "--level LEVEL", Integer, "Compression level (1-9)") do |level|
options[:level] = level
end
end.parse!
Short options use single dashes followed by single characters, while long options use double dashes followed by words. OptionParser accepts both formats simultaneously for the same option. Arguments can be required (no brackets) or optional (enclosed in brackets).
The processing block receives the parsed argument value. For flag options without arguments, the block receives true
. For options with arguments, the block receives the argument string or converted value based on the specified type.
options = {}
parser = OptionParser.new do |opts|
# Flag option - no argument
opts.on("-q", "--quiet") { options[:quiet] = true }
# Required argument
opts.on("-u", "--user USER") { |user| options[:user] = user }
# Optional argument with default
opts.on("-t", "--timeout [SECONDS]") do |timeout|
options[:timeout] = timeout || "30"
end
end
parser.parse!(["-q", "-u", "admin", "-t", "60"])
# options = { quiet: true, user: "admin", timeout: "60" }
OptionParser supports automatic type conversion by specifying a class or conversion object as the third argument to #on
. Common types include Integer, Float, Array, and custom conversion patterns.
options = {}
OptionParser.new do |opts|
opts.on("-p", "--port PORT", Integer) { |p| options[:port] = p }
opts.on("-r", "--ratio RATIO", Float) { |r| options[:ratio] = r }
opts.on("-i", "--include DIRS", Array) { |dirs| options[:include] = dirs }
# Custom pattern matching
opts.on("-m", "--mode MODE", %w[read write append]) do |mode|
options[:mode] = mode
end
end
parser.parse!(["-p", "8080", "-r", "1.5", "-i", "a,b,c", "-m", "write"])
# options = { port: 8080, ratio: 1.5, include: ["a", "b", "c"], mode: "write" }
Help text generation occurs automatically based on option definitions. The banner
attribute sets the usage line, while option descriptions appear in the help output. OptionParser generates formatted help text showing all defined options and their descriptions.
parser = OptionParser.new do |opts|
opts.banner = "Usage: deploy.rb [options] environment"
opts.version = "1.2.0"
opts.on("-d", "--debug", "Enable debug mode") { }
opts.on("-c", "--config FILE", "Configuration file") { }
opts.on_tail("-h", "--help", "Show this help") do
puts opts
exit
end
opts.on_tail("--version", "Show version") do
puts opts.ver
exit
end
end
Error Handling & Debugging
OptionParser raises specific exceptions for different error conditions during argument parsing. Understanding these exceptions enables proper error handling and user feedback in command-line applications.
OptionParser::InvalidOption
occurs when the parser encounters an unrecognized option. This exception includes the problematic option in its message and provides access to the invalid option through the #args
method.
options = {}
parser = OptionParser.new do |opts|
opts.on("-v", "--verbose") { options[:verbose] = true }
end
begin
parser.parse!(["-x", "-v"])
rescue OptionParser::InvalidOption => e
puts "Unknown option: #{e}"
puts "Available options:"
puts parser.help
exit 1
end
OptionParser::MissingArgument
signals that an option requiring an argument was provided without one. This commonly occurs when users specify options at the end of the command line or follow them with other options instead of arguments.
begin
parser.parse!(["-o"]) # Missing required argument
rescue OptionParser::MissingArgument => e
puts "Option #{e.args.first} requires an argument"
exit 1
end
Type conversion failures raise OptionParser::InvalidArgument
when the provided argument cannot be converted to the specified type. This exception occurs during automatic type conversion for Integer, Float, and custom type specifications.
parser = OptionParser.new do |opts|
opts.on("-p", "--port PORT", Integer) { |p| options[:port] = p }
end
begin
parser.parse!(["-p", "not_a_number"])
rescue OptionParser::InvalidArgument => e
puts "Invalid argument: #{e}"
puts "Port must be a number"
exit 1
end
Comprehensive error handling wraps the parsing operation and provides specific feedback for each error type. This approach improves user experience by offering clear error messages and usage guidance.
def parse_options(args)
options = {}
parser = OptionParser.new do |opts|
opts.banner = "Usage: processor.rb [options] files..."
opts.on("-w", "--workers COUNT", Integer, "Worker threads") do |count|
options[:workers] = count
end
opts.on("-f", "--format FORMAT", %w[json xml csv], "Output format") do |format|
options[:format] = format
end
end
begin
remaining = parser.parse(args)
if remaining.empty?
raise "No input files specified"
end
[options, remaining]
rescue OptionParser::InvalidOption => e
$stderr.puts "Error: #{e}"
$stderr.puts parser.help
exit 1
rescue OptionParser::MissingArgument => e
$stderr.puts "Error: #{e}"
exit 1
rescue OptionParser::InvalidArgument => e
$stderr.puts "Error: #{e}"
exit 1
rescue => e
$stderr.puts "Error: #{e}"
exit 1
end
end
Debugging option parsing issues often requires examining the argument array at different stages. Adding debug output shows how OptionParser processes arguments and identifies parsing problems.
parser = OptionParser.new do |opts|
opts.on("-d", "--debug") { options[:debug] = true }
opts.on("-v", "--verbose") { options[:verbose] = true }
end
puts "Original args: #{ARGV.inspect}"
parser.parse!
puts "Remaining args: #{ARGV.inspect}"
puts "Options: #{options.inspect}"
Validation logic for parsed options should occur after successful parsing. OptionParser handles syntactic validation (correct option format, type conversion) but not semantic validation (valid ranges, file existence, etc.).
options, files = parse_options(ARGV)
# Semantic validation after parsing
if options[:workers] && options[:workers] < 1
$stderr.puts "Worker count must be positive"
exit 1
end
if options[:format] == 'xml' && !options[:stylesheet]
$stderr.puts "XML format requires stylesheet option"
exit 1
end
Production Patterns
Production command-line applications require robust option parsing that handles edge cases, provides clear feedback, and integrates with logging and configuration systems. OptionParser patterns for production code emphasize error handling, user experience, and maintainability.
Configuration precedence systems use OptionParser alongside configuration files and environment variables. Command-line options typically override configuration file settings, which override default values. This pattern provides flexibility while maintaining predictable behavior.
class AppConfig
attr_accessor :host, :port, :debug, :log_level
def initialize
@host = ENV['APP_HOST'] || 'localhost'
@port = ENV['APP_PORT']&.to_i || 3000
@debug = false
@log_level = 'info'
end
def parse_options(args = ARGV)
parser = OptionParser.new do |opts|
opts.banner = "Usage: myapp [options]"
opts.on("-h", "--host HOST", "Server host") { |h| @host = h }
opts.on("-p", "--port PORT", Integer, "Server port") { |p| @port = p }
opts.on("-d", "--debug", "Debug mode") { @debug = true }
opts.on("-l", "--log-level LEVEL", %w[debug info warn error],
"Log level") { |l| @log_level = l }
opts.on_tail("--help", "Show help") do
puts opts
exit
end
end
begin
parser.parse!(args)
validate_config
rescue OptionParser::ParseError => e
$stderr.puts "Error: #{e}"
$stderr.puts parser
exit 1
end
self
end
private
def validate_config
unless (1..65535).include?(@port)
raise "Port must be between 1 and 65535"
end
end
end
Subcommand patterns split complex applications into focused command modules. Each subcommand defines its own option parser while sharing common options through inheritance or composition.
class CLIApp
def initialize
@global_options = {}
end
def run(args = ARGV)
parser = OptionParser.new do |opts|
opts.banner = "Usage: myapp [global_options] command [command_options]"
opts.on("-v", "--verbose", "Verbose output") do
@global_options[:verbose] = true
end
opts.on("-c", "--config FILE", "Config file") do |file|
@global_options[:config] = file
end
end
# Parse global options, stopping at first non-option
parser.order!(args)
command = args.shift
case command
when 'deploy'
DeployCommand.new(@global_options).run(args)
when 'backup'
BackupCommand.new(@global_options).run(args)
else
puts "Unknown command: #{command}"
puts parser
exit 1
end
end
end
class DeployCommand
def initialize(global_options)
@global_options = global_options
@options = { strategy: 'rolling' }
end
def run(args)
parser = OptionParser.new do |opts|
opts.banner = "Usage: myapp deploy [options] environment"
opts.on("-s", "--strategy STRATEGY", %w[rolling blue-green],
"Deployment strategy") { |s| @options[:strategy] = s }
opts.on("-t", "--timeout SECONDS", Integer,
"Deployment timeout") { |t| @options[:timeout] = t }
end
parser.parse!(args)
environment = args.first
unless environment
puts "Environment required"
puts parser
exit 1
end
deploy(environment)
end
private
def deploy(environment)
puts "Deploying to #{environment} using #{@options[:strategy]} strategy"
puts "Global config: #{@global_options[:config]}" if @global_options[:config]
end
end
Logging integration connects command-line options with application logging systems. Production applications often allow log level and destination configuration through command-line options.
require 'logger'
class LoggedApp
def initialize
@options = {}
setup_option_parser
end
def run(args = ARGV)
@parser.parse!(args)
@logger = create_logger
@logger.info "Application starting with options: #{@options}"
begin
execute_main_logic
rescue => e
@logger.error "Application error: #{e}"
@logger.debug e.backtrace.join("\n") if @options[:debug]
exit 1
end
end
private
def setup_option_parser
@parser = OptionParser.new do |opts|
opts.banner = "Usage: logapp [options]"
opts.on("--log-file FILE", "Log to file") { |f| @options[:log_file] = f }
opts.on("--log-level LEVEL", %w[DEBUG INFO WARN ERROR FATAL],
"Log level") { |l| @options[:log_level] = l }
opts.on("--debug", "Debug mode") { @options[:debug] = true }
opts.on("-q", "--quiet", "Quiet mode") { @options[:quiet] = true }
end
end
def create_logger
output = @options[:log_file] ? File.open(@options[:log_file], 'a') : $stdout
logger = Logger.new(output)
level = if @options[:quiet]
Logger::ERROR
elsif @options[:debug]
Logger::DEBUG
else
Logger.const_get(@options[:log_level] || 'INFO')
end
logger.level = level
logger.formatter = proc do |severity, datetime, progname, msg|
"[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{severity}: #{msg}\n"
end
logger
end
def execute_main_logic
@logger.info "Executing main application logic"
# Application logic here
end
end
Common Pitfalls
OptionParser behavior includes several edge cases and gotchas that can cause unexpected results in command-line parsing. Understanding these pitfalls prevents common mistakes and improves application reliability.
Option ordering affects parsing results when using #parse!
versus #parse
. The destructive #parse!
method modifies ARGV in place, which can cause issues if the application expects to access original arguments after parsing.
original_args = ["-v", "--port", "8080", "input.txt"]
args_copy = original_args.dup
parser = OptionParser.new do |opts|
opts.on("-v", "--verbose") { }
opts.on("--port PORT", Integer) { }
end
parser.parse!(args_copy)
puts "Original: #{original_args}" # ["-v", "--port", "8080", "input.txt"]
puts "After parse!: #{args_copy}" # ["input.txt"]
# Use parse instead to preserve original
result = parser.parse(original_args)
puts "Non-destructive: #{result}" # ["input.txt"]
Argument separation issues occur when option arguments contain spaces or special characters. Users must quote arguments containing spaces, but applications may need to handle both quoted and unquoted inputs appropriately.
# Problematic: spaces in arguments
# Command line: myapp --message Hello World --file data.txt
# Parsed as: message="Hello", and "World" becomes a non-option argument
parser = OptionParser.new do |opts|
opts.on("-m", "--message MSG", "Message text") { |msg| puts "Message: #{msg}" }
opts.on("-f", "--file FILE", "Input file") { |file| puts "File: #{file}" }
end
# Correct usage requires quoting
# myapp --message "Hello World" --file data.txt
# Or: myapp --message="Hello World" --file=data.txt
Boolean option handling can be counterintuitive when dealing with default values and negation. OptionParser does not automatically provide negation options, and false defaults require explicit handling.
options = { verbose: false, debug: true } # Note different defaults
parser = OptionParser.new do |opts|
opts.on("-v", "--verbose", "Enable verbose mode") { options[:verbose] = true }
opts.on("-q", "--quiet", "Disable verbose mode") { options[:verbose] = false }
# No automatic --no-debug option created
opts.on("-d", "--debug", "Enable debug mode") { options[:debug] = true }
opts.on("--no-debug", "Disable debug mode") { options[:debug] = false }
end
# Users might expect --no-verbose to work, but it doesn't
begin
parser.parse!(["--no-verbose"])
rescue OptionParser::InvalidOption => e
puts "Error: #{e}" # "invalid option: --no-verbose"
end
Type conversion edge cases cause parsing failures when arguments don't match expected formats. OptionParser's automatic conversion can be too strict or too lenient depending on the use case.
parser = OptionParser.new do |opts|
opts.on("-p", "--port PORT", Integer) { |p| puts "Port: #{p}" }
opts.on("-r", "--rate RATE", Float) { |r| puts "Rate: #{r}" }
end
# These work as expected
parser.parse!(["-p", "8080"]) # Port: 8080
parser.parse!(["-r", "1.5"]) # Rate: 1.5
# But these might surprise users
begin
parser.parse!(["-p", "8080.5"]) # Fails - Float to Integer conversion
rescue OptionParser::InvalidArgument => e
puts "Error: #{e}"
end
begin
parser.parse!(["-p", "08080"]) # Succeeds - becomes 4160 (octal!)
rescue OptionParser::InvalidArgument => e
puts "Port parsed as octal: unexpected result"
end
Array option parsing splits arguments on commas by default, but this behavior might not match user expectations for file paths or other data containing commas.
options = {}
parser = OptionParser.new do |opts|
opts.on("-f", "--files FILES", Array, "Input files") { |files| options[:files] = files }
end
parser.parse!(["-f", "a.txt,b.txt,c.txt"])
puts options[:files] # ["a.txt", "b.txt", "c.txt"]
# Problematic with file paths containing commas
parser.parse!(["-f", "My Document, version 1.txt,other.txt"])
puts options[:files] # ["My Document", " version 1.txt", "other.txt"]
Option precedence and multiple definitions can create confusion when the same option appears multiple times. OptionParser executes the block for each occurrence, which may not match expected behavior.
count = 0
parser = OptionParser.new do |opts|
opts.on("-v", "--verbose", "Increase verbosity") { count += 1 }
end
parser.parse!(["-v", "-v", "--verbose"])
puts "Verbosity level: #{count}" # 3
# Alternative approach for accumulating options
verbosity = 0
parser = OptionParser.new do |opts|
opts.on("-v", "--verbose", "Increase verbosity") { verbosity += 1 }
opts.on("-q", "--quiet", "Decrease verbosity") { verbosity -= 1 }
end
Error message clarity suffers when option descriptions are unclear or when the help text doesn't match actual behavior. Production applications should provide clear, actionable error messages.
# Poor error handling
parser = OptionParser.new do |opts|
opts.on("-x") { } # No description, confusing help
end
begin
parser.parse!(["--unknown"])
rescue OptionParser::InvalidOption
puts parser.help # Shows -x with no explanation
end
# Better error handling with context
parser = OptionParser.new do |opts|
opts.banner = "Usage: myapp [options] files..."
opts.on("-x", "--extract", "Extract files from archive") { }
opts.on_tail("-h", "--help", "Show this help message") do
puts opts
puts "\nExamples:"
puts " myapp -x archive.tar files/"
puts " myapp --extract --verbose archive.zip"
exit
end
end
Reference
Core Methods
Method | Parameters | Returns | Description |
---|---|---|---|
OptionParser.new(&block) |
block (optional) |
OptionParser |
Creates new parser, yields to configuration block |
#on(*args, &block) |
args (option patterns), block |
self |
Defines option with processing block |
#parse(argv) |
argv (Array) |
Array |
Parses arguments, returns remaining args |
#parse!(argv) |
argv (Array) |
Array |
Destructively parses arguments |
#order(argv) |
argv (Array) |
Array |
Parses options in order, stops at first non-option |
#order!(argv) |
argv (Array) |
Array |
Destructively parses options in order |
#permute(argv) |
argv (Array) |
Array |
Parses options throughout argument list |
#permute!(argv) |
argv (Array) |
Array |
Destructively parses options throughout list |
Option Definition Patterns
Pattern | Example | Description |
---|---|---|
Short flag | -v |
Single dash, single character, no argument |
Long flag | --verbose |
Double dash, word, no argument |
Short with argument | -f FILE |
Short option requiring argument |
Long with argument | --file FILE |
Long option requiring argument |
Optional argument | -p [PORT] |
Argument in brackets, optional |
Combined formats | -f , --file FILE |
Short and long variants |
Type Conversion Classes
Type | Example Usage | Conversion Result |
---|---|---|
Integer |
opts.on("-p PORT", Integer) |
Converts string to integer |
Float |
opts.on("-r RATE", Float) |
Converts string to float |
Numeric |
opts.on("-n NUM", Numeric) |
Converts to Integer or Float |
TrueClass |
opts.on("-v", TrueClass) |
Always returns true |
FalseClass |
opts.on("-q", FalseClass) |
Always returns false |
Array |
opts.on("-l LIST", Array) |
Splits on commas |
Regexp |
opts.on("-m REGEX", Regexp) |
Compiles string to regexp |
Exception Hierarchy
Exception | Cause | Recovery Strategy |
---|---|---|
OptionParser::ParseError |
Base class for parsing errors | Catch-all for option errors |
OptionParser::InvalidOption |
Unknown option encountered | Show help, suggest similar options |
OptionParser::InvalidArgument |
Argument type conversion failed | Validate input format, show examples |
OptionParser::MissingArgument |
Required argument missing | Prompt for argument, show usage |
OptionParser::AmbiguousOption |
Option matches multiple definitions | List matching options |
Banner and Help Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#banner= |
string |
String |
Sets usage line in help text |
#program_name= |
string |
String |
Sets program name for help |
#version= |
string |
String |
Sets version for --version option |
#ver |
none | String |
Returns version string |
#help |
none | String |
Returns formatted help text |
#to_s |
none | String |
Alias for help method |
#on_tail(*args, &block) |
args , block |
self |
Adds option at end of help |
#on_head(*args, &block) |
args , block |
self |
Adds option at start of help |
Parsing Behavior Options
Method | Effect | Use Case |
---|---|---|
#parse |
Non-destructive parsing | When original ARGV needed |
#parse! |
Modifies input array | Standard CLI processing |
#order |
Stops at first non-option | Subcommand parsing |
#permute |
Processes options throughout | Mixed options and arguments |
Common Option Patterns
# Help and version options
opts.on_tail("-h", "--help", "Show help") { puts opts; exit }
opts.on_tail("--version", "Show version") { puts opts.ver; exit }
# Verbose and quiet modes
opts.on("-v", "--verbose", "Verbose output") { options[:verbose] = true }
opts.on("-q", "--quiet", "Minimal output") { options[:quiet] = true }
# Configuration file
opts.on("-c", "--config FILE", "Config file path") { |f| options[:config] = f }
# Numeric options with validation
opts.on("-p", "--port PORT", Integer, "Port (1-65535)") do |p|
unless (1..65535).include?(p)
raise OptionParser::InvalidArgument, "Port must be 1-65535"
end
options[:port] = p
end
# Multiple choice options
opts.on("-m", "--mode MODE", %w[fast normal secure], "Processing mode") do |m|
options[:mode] = m
end