Overview
GetoptLong provides command-line option parsing similar to GNU getopt_long(), handling both short options like -v
and long options like --verbose
. Ruby implements GetoptLong as a class that processes ARGV arguments according to defined option specifications, supporting required arguments, optional arguments, and boolean flags.
The parser operates by consuming options from ARGV in order, returning each parsed option as a name-value pair. GetoptLong supports POSIX-style option clustering (combining multiple short options like -abc
) and GNU-style long options with equals assignment (--output=file.txt
).
require 'getoptlong'
opts = GetoptLong.new(
['--verbose', '-v', GetoptLong::NO_ARGUMENT],
['--file', '-f', GetoptLong::REQUIRED_ARGUMENT]
)
opts.each do |opt, arg|
case opt
when '--verbose'
puts "Verbose mode enabled"
when '--file'
puts "Processing file: #{arg}"
end
end
GetoptLong modifies ARGV by removing processed options, leaving remaining arguments for application use. The parser stops at the first non-option argument or when encountering the end-of-options marker --
.
# Command: ruby script.rb --verbose --file data.txt input1 input2
# After parsing: ARGV contains ['input1', 'input2']
GetoptLong defines three argument types: NO_ARGUMENT
for boolean flags, REQUIRED_ARGUMENT
for options requiring values, and OPTIONAL_ARGUMENT
for options with optional values. The parser validates argument requirements and raises exceptions for malformed options.
Basic Usage
GetoptLong requires option specifications defining the option names, aliases, and argument types. Each specification contains the long option name, optional short alias, and argument requirement constant.
require 'getoptlong'
opts = GetoptLong.new(
['--help', '-h', GetoptLong::NO_ARGUMENT],
['--output', '-o', GetoptLong::REQUIRED_ARGUMENT],
['--count', '-c', GetoptLong::OPTIONAL_ARGUMENT],
['--debug', GetoptLong::NO_ARGUMENT]
)
The each
method yields option-argument pairs for processing. The parser returns the canonical long option name regardless of which alias the user provided.
verbose = false
output_file = nil
debug_level = 0
opts.each do |option, argument|
case option
when '--help'
puts "Usage: #{$0} [options] files..."
exit 0
when '--output'
output_file = argument
when '--count'
count = argument.empty? ? 10 : argument.to_i
when '--debug'
debug_level += 1
end
end
puts "Remaining arguments: #{ARGV.join(', ')}" unless ARGV.empty?
GetoptLong supports option ordering modes that control how options and non-option arguments intermix. The default PERMUTE
mode moves options before non-option arguments, while REQUIRE_ORDER
stops parsing at the first non-option.
opts = GetoptLong.new(
['--verbose', '-v', GetoptLong::NO_ARGUMENT]
)
opts.ordering = GetoptLong::REQUIRE_ORDER
# Command: ruby script.rb file1 --verbose file2
# Only processes file1, stops before --verbose
Multiple instances of the same option accumulate through repeated iteration. Applications typically use counters or arrays to handle repeated options.
verbose_level = 0
include_paths = []
opts.each do |option, argument|
case option
when '--verbose'
verbose_level += 1 # -vvv increases level to 3
when '--include'
include_paths << argument
end
end
The parser recognizes several option formats: short options (-v
), clustered short options (-abc
), long options (--verbose
), and long options with arguments (--file=name
or --file name
). GetoptLong handles argument separation automatically.
# These are equivalent:
# --output=report.txt
# --output report.txt
# -o report.txt
# -oreport.txt
opts = GetoptLong.new(
['--output', '-o', GetoptLong::REQUIRED_ARGUMENT]
)
opts.each do |opt, arg|
puts "Output file: #{arg}" # Always receives 'report.txt'
end
Error Handling & Debugging
GetoptLong raises GetoptLong::InvalidOption
for unrecognized options and GetoptLong::MissingArgument
for options missing required arguments. Applications should wrap parsing in exception handlers to provide user-friendly error messages.
require 'getoptlong'
opts = GetoptLong.new(
['--file', '-f', GetoptLong::REQUIRED_ARGUMENT],
['--count', '-c', GetoptLong::REQUIRED_ARGUMENT]
)
begin
opts.each do |option, argument|
case option
when '--file'
unless File.exist?(argument)
STDERR.puts "Error: File '#{argument}' not found"
exit 1
end
@input_file = argument
when '--count'
@count = Integer(argument)
end
end
rescue GetoptLong::InvalidOption => e
STDERR.puts "Unknown option: #{e.message}"
STDERR.puts "Use --help for usage information"
exit 1
rescue GetoptLong::MissingArgument => e
STDERR.puts "Option requires an argument: #{e.message}"
exit 1
rescue ArgumentError => e
STDERR.puts "Invalid argument format: #{e.message}"
exit 1
end
The quiet
attribute controls whether GetoptLong prints error messages to STDERR. Setting quiet
to true suppresses automatic error output, giving applications full control over error presentation.
opts = GetoptLong.new(
['--verbose', '-v', GetoptLong::NO_ARGUMENT]
)
opts.quiet = true
begin
opts.each { |opt, arg| puts "Got #{opt}" }
rescue GetoptLong::InvalidOption => e
puts "Custom error: Unknown option #{e.message}"
end
Debugging option parsing requires understanding how GetoptLong processes ARGV. The get
method returns individual option-argument pairs, allowing step-by-step parsing inspection.
opts = GetoptLong.new(
['--debug', GetoptLong::NO_ARGUMENT],
['--file', GetoptLong::REQUIRED_ARGUMENT]
)
loop do
begin
option, argument = opts.get
break if option.nil?
puts "Parsed: #{option} = #{argument.inspect}"
rescue GetoptLong::InvalidOption => e
puts "Invalid option encountered: #{e}"
break
end
end
puts "Remaining ARGV: #{ARGV.inspect}"
Validation beyond GetoptLong's built-in checks requires custom argument processing. Applications should validate file paths, numeric ranges, and enum values after option parsing completes.
def validate_options(options)
errors = []
if options[:count] && options[:count] <= 0
errors << "Count must be positive"
end
if options[:format] && !%w[json xml csv].include?(options[:format])
errors << "Format must be json, xml, or csv"
end
if options[:input] && !File.readable?(options[:input])
errors << "Cannot read input file: #{options[:input]}"
end
unless errors.empty?
STDERR.puts "Validation errors:"
errors.each { |error| STDERR.puts " #{error}" }
exit 1
end
end
Common Pitfalls
Option specifications must include the long option name first, followed by optional short aliases, then the argument type. Reversing this order or omitting the argument type causes runtime errors.
# Wrong - argument type missing
opts = GetoptLong.new(['--verbose', '-v'])
# Wrong - short option first
opts = GetoptLong.new(['-v', '--verbose', GetoptLong::NO_ARGUMENT])
# Correct
opts = GetoptLong.new(['--verbose', '-v', GetoptLong::NO_ARGUMENT])
GetoptLong modifies ARGV during parsing, which can cause issues when multiple parsers process the same arguments or when applications need the original arguments. Save ARGV before parsing if the original arguments are needed.
original_args = ARGV.dup
opts = GetoptLong.new(['--verbose', GetoptLong::NO_ARGUMENT])
opts.each { |opt, arg| puts "Processing #{opt}" }
puts "Original: #{original_args.join(' ')}"
puts "Modified: #{ARGV.join(' ')}"
Clustered short options like -abc
expand to -a -b -c
, but only if all options are NO_ARGUMENT
type. Mixing argument-requiring options in clusters produces unexpected behavior.
opts = GetoptLong.new(
['--all', '-a', GetoptLong::NO_ARGUMENT],
['--file', '-f', GetoptLong::REQUIRED_ARGUMENT],
['--count', '-c', GetoptLong::REQUIRED_ARGUMENT]
)
# Command: ruby script.rb -af output.txt
# GetoptLong interprets this as: -a -f output.txt
# NOT as: -a -f=output.txt or -af=output.txt
# Safe clustering (all NO_ARGUMENT):
# ruby script.rb -abc
# Expands to: -a -b -c
The OPTIONAL_ARGUMENT
type behaves differently between short and long options. Long options require equals syntax for optional arguments, while short options consume the next argument if it doesn't start with a dash.
opts = GetoptLong.new(
['--level', '-l', GetoptLong::OPTIONAL_ARGUMENT]
)
# These work correctly:
# --level=5 (argument is "5")
# --level (argument is "")
# -l5 (argument is "5")
# -l (argument is "" if next arg starts with -)
# This might not work as expected:
# -l 5 (argument might be "5" or "" depending on context)
GetoptLong stops parsing at the first unrecognized argument when ordering
is REQUIRE_ORDER
, but continues parsing remaining options in PERMUTE
mode. This can lead to inconsistent behavior across different option orderings.
opts = GetoptLong.new(['--verbose', GetoptLong::NO_ARGUMENT])
opts.ordering = GetoptLong::REQUIRE_ORDER
# Command: ruby script.rb input.txt --verbose
# Result: ARGV = ['input.txt', '--verbose'] (--verbose not parsed)
opts.ordering = GetoptLong::PERMUTE # Default
# Command: ruby script.rb input.txt --verbose
# Result: ARGV = ['input.txt'] (--verbose parsed and removed)
Exception handling must account for GetoptLong's parsing state. Once an exception occurs, the parser becomes unusable and subsequent calls to each
or get
may produce inconsistent results.
opts = GetoptLong.new(['--count', GetoptLong::REQUIRED_ARGUMENT])
begin
opts.each do |opt, arg|
puts "Processing #{opt}: #{arg}"
end
rescue GetoptLong::MissingArgument
puts "Error occurred - parser state is undefined"
# Don't continue using opts after exception
end
Production Patterns
Command-line applications benefit from structured option handling that separates parsing from business logic. Create a configuration class that encapsulates option processing and validation.
require 'getoptlong'
class ApplicationConfig
attr_reader :verbose, :input_files, :output_format, :max_threads
def initialize(argv = ARGV)
@verbose = false
@input_files = []
@output_format = 'text'
@max_threads = 1
@help_requested = false
parse_options
validate_configuration
@input_files = argv.dup # Remaining arguments after option parsing
end
def show_help?
@help_requested
end
private
def parse_options
opts = GetoptLong.new(
['--help', '-h', GetoptLong::NO_ARGUMENT],
['--verbose', '-v', GetoptLong::NO_ARGUMENT],
['--format', '-f', GetoptLong::REQUIRED_ARGUMENT],
['--threads', '-t', GetoptLong::REQUIRED_ARGUMENT],
['--config', '-c', GetoptLong::REQUIRED_ARGUMENT]
)
opts.quiet = true
opts.each do |option, argument|
case option
when '--help'
@help_requested = true
when '--verbose'
@verbose = true
when '--format'
@output_format = argument.downcase
when '--threads'
@max_threads = Integer(argument)
when '--config'
load_config_file(argument)
end
end
rescue GetoptLong::InvalidOption, GetoptLong::MissingArgument, ArgumentError => e
STDERR.puts "Option error: #{e.message}"
STDERR.puts "Use --help for usage information"
exit 1
end
def validate_configuration
unless %w[text json xml csv].include?(@output_format)
raise "Invalid output format: #{@output_format}"
end
if @max_threads < 1 || @max_threads > 32
raise "Thread count must be between 1 and 32"
end
end
def load_config_file(path)
# Configuration file processing logic
unless File.readable?(path)
raise "Cannot read configuration file: #{path}"
end
end
end
# Usage in main application
begin
config = ApplicationConfig.new
if config.show_help?
puts "Usage: #{$0} [options] input_files..."
puts "Options:"
puts " -h, --help Show this help"
puts " -v, --verbose Enable verbose output"
puts " -f, --format FORMAT Output format (text, json, xml, csv)"
puts " -t, --threads COUNT Number of processing threads"
puts " -c, --config FILE Load configuration from file"
exit 0
end
processor = DataProcessor.new(config)
processor.process_files(config.input_files)
rescue StandardError => e
STDERR.puts "Error: #{e.message}"
exit 1
end
For complex CLI applications, implement subcommand support by parsing the first argument as a command name, then creating command-specific option parsers.
class CLIApplication
def initialize
@global_verbose = false
@command = nil
@command_args = []
end
def run
parse_global_options
execute_command
end
private
def parse_global_options
opts = GetoptLong.new(
['--verbose', '-v', GetoptLong::NO_ARGUMENT],
['--help', '-h', GetoptLong::NO_ARGUMENT]
)
opts.ordering = GetoptLong::REQUIRE_ORDER
opts.each do |option, argument|
case option
when '--verbose'
@global_verbose = true
when '--help'
show_global_help
exit 0
end
end
if ARGV.empty?
STDERR.puts "No command specified"
show_global_help
exit 1
end
@command = ARGV.shift
@command_args = ARGV.dup
end
def execute_command
case @command
when 'process'
ProcessCommand.new(@command_args, @global_verbose).execute
when 'analyze'
AnalyzeCommand.new(@command_args, @global_verbose).execute
when 'report'
ReportCommand.new(@command_args, @global_verbose).execute
else
STDERR.puts "Unknown command: #{@command}"
exit 1
end
end
def show_global_help
puts "Usage: #{$0} [global-options] command [command-options]"
puts "Global options:"
puts " -v, --verbose Enable verbose output"
puts " -h, --help Show this help"
puts ""
puts "Commands:"
puts " process Process input files"
puts " analyze Analyze data patterns"
puts " report Generate reports"
end
end
class ProcessCommand
def initialize(args, verbose)
@args = args
@global_verbose = verbose
@input_files = []
@output_dir = '.'
@parallel = false
end
def execute
parse_options
if @input_files.empty?
STDERR.puts "No input files specified"
exit 1
end
puts "Processing #{@input_files.length} files..." if @global_verbose
# Processing logic here
end
private
def parse_options
ARGV.replace(@args) # Reset ARGV for this command's parsing
opts = GetoptLong.new(
['--input', '-i', GetoptLong::REQUIRED_ARGUMENT],
['--output', '-o', GetoptLong::REQUIRED_ARGUMENT],
['--parallel', '-p', GetoptLong::NO_ARGUMENT]
)
opts.each do |option, argument|
case option
when '--input'
@input_files << argument
when '--output'
@output_dir = argument
when '--parallel'
@parallel = true
end
end
@input_files.concat(ARGV) # Add remaining arguments as input files
end
end
Production applications should implement comprehensive logging that captures both option parsing decisions and application behavior. This facilitates troubleshooting deployment issues and user error reports.
require 'logger'
class ProductionCLI
def initialize
@logger = Logger.new(STDERR)
@logger.level = Logger::WARN
@options = {}
end
def run
parse_options
setup_logging
execute_application
end
private
def parse_options
opts = GetoptLong.new(
['--log-level', GetoptLong::REQUIRED_ARGUMENT],
['--log-file', GetoptLong::REQUIRED_ARGUMENT],
['--dry-run', GetoptLong::NO_ARGUMENT],
['--config', GetoptLong::REQUIRED_ARGUMENT]
)
opts.each do |option, argument|
@logger.debug "Parsing option: #{option} = #{argument}"
case option
when '--log-level'
@options[:log_level] = argument.upcase
when '--log-file'
@options[:log_file] = argument
when '--dry-run'
@options[:dry_run] = true
when '--config'
@options[:config_file] = argument
end
end
@logger.info "Option parsing completed: #{@options.inspect}"
end
def setup_logging
if @options[:log_level]
@logger.level = Logger.const_get(@options[:log_level])
end
if @options[:log_file]
@logger = Logger.new(@options[:log_file])
@logger.level = Logger.const_get(@options[:log_level] || 'INFO')
end
@logger.info "Application started with options: #{@options.inspect}"
@logger.info "Remaining arguments: #{ARGV.inspect}"
end
def execute_application
@logger.info "Beginning application execution"
if @options[:dry_run]
@logger.info "Dry run mode - no changes will be made"
end
# Application logic with comprehensive logging
@logger.info "Application execution completed successfully"
rescue StandardError => e
@logger.error "Application failed: #{e.message}"
@logger.error e.backtrace.join("\n")
exit 1
end
end
Reference
GetoptLong Class Methods
Method | Parameters | Returns | Description |
---|---|---|---|
new(*arguments) |
*arguments (Array) |
GetoptLong |
Creates parser with option specifications |
GetoptLong Instance Methods
Method | Parameters | Returns | Description |
---|---|---|---|
each |
&block |
GetoptLong |
Iterates over parsed options yielding name, argument |
get |
None | Array |
Returns next option as [name, argument] or [nil, nil] |
ordering=(mode) |
mode (Integer) |
Integer |
Sets option ordering mode |
ordering |
None | Integer |
Returns current ordering mode |
quiet=(boolean) |
boolean (Boolean) |
Boolean |
Sets error message suppression |
quiet |
None | Boolean |
Returns error suppression setting |
terminate |
None | GetoptLong |
Terminates option processing |
terminated? |
None | Boolean |
Returns true if processing terminated |
Argument Type Constants
Constant | Value | Description |
---|---|---|
NO_ARGUMENT |
0 | Option takes no argument (boolean flag) |
REQUIRED_ARGUMENT |
1 | Option requires an argument |
OPTIONAL_ARGUMENT |
2 | Option argument is optional |
Ordering Mode Constants
Constant | Value | Description |
---|---|---|
PERMUTE |
0 | Permute arguments (default) - options processed anywhere |
REQUIRE_ORDER |
1 | Stop processing at first non-option argument |
RETURN_IN_ORDER |
2 | Return non-options as arguments to option '' |
Exception Classes
Exception | Inheritance | Description |
---|---|---|
GetoptLong::Error |
StandardError |
Base class for GetoptLong exceptions |
GetoptLong::AmbiguousOption |
GetoptLong::Error |
Ambiguous option abbreviation |
GetoptLong::InvalidOption |
GetoptLong::Error |
Unrecognized option |
GetoptLong::MissingArgument |
GetoptLong::Error |
Required argument missing |
GetoptLong::NeedlessArgument |
GetoptLong::Error |
Argument provided to NO_ARGUMENT option |
Option Specification Format
# Format: [long_option, short_option, argument_type]
['--verbose', '-v', GetoptLong::NO_ARGUMENT]
['--file', '-f', GetoptLong::REQUIRED_ARGUMENT]
['--count', GetoptLong::OPTIONAL_ARGUMENT] # No short option
Supported Option Formats
Format | Example | Argument Type | Description |
---|---|---|---|
-v |
-v |
NO_ARGUMENT |
Short boolean flag |
-f arg |
-f file.txt |
REQUIRED_ARGUMENT |
Short option with separate argument |
-farg |
-ffile.txt |
REQUIRED_ARGUMENT |
Short option with attached argument |
--flag |
--verbose |
NO_ARGUMENT |
Long boolean flag |
--opt arg |
--file data.txt |
REQUIRED_ARGUMENT |
Long option with separate argument |
--opt=arg |
--file=data.txt |
REQUIRED_ARGUMENT |
Long option with equals argument |
-abc |
-abc |
All NO_ARGUMENT |
Clustered short options |
Common Usage Patterns
# Basic option parsing
opts = GetoptLong.new(
['--help', '-h', GetoptLong::NO_ARGUMENT],
['--verbose', '-v', GetoptLong::NO_ARGUMENT],
['--file', '-f', GetoptLong::REQUIRED_ARGUMENT]
)
# Error handling pattern
begin
opts.each do |opt, arg|
# Process options
end
rescue GetoptLong::InvalidOption, GetoptLong::MissingArgument => e
STDERR.puts "Error: #{e.message}"
exit 1
end
# Accumulating repeated options
count = 0
opts.each do |opt, arg|
case opt
when '--verbose'
count += 1
end
end