Building CLIs with OptionParser
· 7 min read · Updated March 20, 2026 · beginner
ruby cli command-line stdlib
Introduction
Every Ruby script that accepts user input from the command line needs a way to handle arguments like -f filename or --verbose. Ruby’s standard library ships with OptionParser, a powerful and user-friendly tool for this exact purpose.
OptionParser handles the boring parts for you. It automatically generates help text, coerces arguments into the right types, and raises helpful error messages when something goes wrong. This guide walks you through everything you need to build a polished CLI in Ruby.
Setting Up a Parser
The first step is to require the library and create an OptionParser instance. You configure the parser by passing a block, where you set the banner and define your options.
require 'optparse'
options = {}
parser = OptionParser.new do |opts|
opts.banner = "Usage: myapp.rb [options]"
end
parser.parse!
```ruby
Running this script with `-h` or `--help` prints the banner:
```bash
Usage: myapp.rb [options]
```ruby
By itself, the parser does not do much. You need to add options to it.
## Defining Your First Option
You define options inside the block using the `on` method. Each call to `on` specifies the short flag, long flag, an optional description, and a block that receives the parsed value.
```ruby
require 'optparse'
options = {}
parser = OptionParser.new do |opts|
opts.banner = "Usage: myapp.rb [options]"
opts.on("-n", "--name NAME", "Your name") do |name|
options[:name] = name
end
end
parser.parse!
p options
```ruby
Try running it:
```bash
$ ruby myapp.rb --name Alice
{:name=>"Alice"}
```ruby
The `--name NAME` syntax tells OptionParser that `NAME` is a required argument. If a user runs the script without providing a value, OptionParser raises `OptionParser::MissingArgument`.
## Boolean Flags
Many CLI tools have flags that simply toggle a setting on or off, like `--verbose` or `--quiet`. OptionParser supports this with a special syntax that creates both `--verbose` and `--no-verbose` flags.
```ruby
require 'optparse'
options = {}
parser = OptionParser.new do |opts|
opts.on("--[no-]verbose", "Enable verbose output") do |v|
options[:verbose] = v
end
end
parser.parse!
p options
```ruby
Running this demonstrates the paired flags:
```bash
$ ruby myapp.rb --verbose
{:verbose=>true}
$ ruby myapp.rb --no-verbose
{:verbose=>false}
```ruby
The `[no-]` prefix is the shorthand for this pattern. OptionParser expands it into two separate flags automatically.
## Type Coercion
One of OptionParser's most useful features is automatic type coercion. Instead of receiving everything as a string, you can specify the type and OptionParser converts it for you.
```ruby
require 'optparse'
options = {}
parser = OptionParser.new do |opts|
opts.on("-p", "--port PORT", Integer, "Port number") do |port|
options[:port] = port
end
opts.on("-t", "--tags TAG1,TAG2", Array, "Comma-separated tags") do |tags|
options[:tags] = tags
end
end
parser.parse!
p options
```ruby
Coercion works for several built-in types:
```bash
$ ruby myapp.rb -p 8080
{:port=>8080, :tags=>nil}
$ ruby myapp.rb -t foo,bar,baz
{:port=>nil, :tags=>["foo", "bar", "baz"]}
```ruby
The available built-in converters include `Integer`, `Float`, `String`, `Array`, `Date`, `URI`, and `Regexp`. You can also restrict values to a specific list:
```ruby
require 'optparse'
options = {}
parser = OptionParser.new do |opts|
opts.on("--format FORMAT", %w[json yaml xml], "Output format") do |fmt|
options[:format] = fmt
end
end
parser.parse!
p options
```ruby
Passing an invalid value raises `OptionParser::InvalidArgument`:
```bash
$ ruby myapp.rb --format csv
# => OptionParser::InvalidArgument: invalid argument: --format=csv
```ruby
## Optional Arguments
By default, arguments like `NAME` in `--name NAME` are required. To make an argument optional, use square brackets around it:
```ruby
require 'optparse'
options = {}
parser = OptionParser.new do |opts|
opts.on("-c", "--count [N]", Integer, "Count of items") do |n|
options[:count] = n
end
end
parser.parse!
p options
```ruby
Optional arguments can be omitted entirely:
```bash
$ ruby myapp.rb -c
{:count=>nil}
$ ruby myapp.rb -c 5
{:count=>5}
```ruby
## Generating Help
Good CLI tools include help text. OptionParser makes this straightforward. Add a `-h` and `--help` option that prints the parser itself and exits.
```ruby
require 'optparse'
options = {}
parser = OptionParser.new do |opts|
opts.banner = "Usage: myapp.rb [options]"
opts.on("-n", "--name NAME", String, "Your name") do |name|
options[:name] = name
end
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
options[:verbose] = v
end
opts.on("-h", "--help", "Show this help") do
puts opts
exit
end
end
parser.parse!
puts "Running with #{options}"
```ruby
Running `ruby myapp.rb --help` produces:
```bash
Usage: myapp.rb [options]
-n, --name NAME Your name
-v, --[no-]verbose Run verbosely
-h, --help Show this help
```ruby
You can also use `parser.separator` to group related options visually, and `on_tail` to list options at the bottom of the help text.
## Using `into:`
OptionParser also supports a cleaner approach with the `into:` parameter, which stores all parsed options directly into a hash without needing individual blocks for each option.
```ruby
require 'optparse'
options = {}
parser = OptionParser.new do |p|
p.on("-n", "--name NAME", "Your name")
p.on("-c", "--count N", Integer, "Count")
p.on("-v", "--[no-]verbose", "Verbose mode")
end
parser.parse!(into: options)
p options
```ruby
```bash
$ ruby myapp.rb --name Alice -c 3 --verbose
{:name=>"Alice", :count=>3, :verbose=>true}
```ruby
The trade-off is that `into:` gives you less control over each individual option. Use the block style when you need to validate or transform values during parsing. Use `into:` when you just want to collect values.
## Choosing a Parsing Method
OptionParser provides several parsing methods with different behaviour:
- `parse!(ARGV)` — parses all arguments, raises on unknown options
- `parse(ARGV)` — same as `parse!` but leaves `ARGV` unchanged
- `order!(ARGV)` — parses in order, stops at the first non-option argument
- `permute!(ARGV)` — allows options and arguments to be mixed freely
```ruby
require 'optparse'
options = {}
parser = OptionParser.new do |p|
p.on("-n", "--name NAME")
p.on("-v", "--[no-]verbose")
end
# order! stops at first non-option, useful for subcommand-style CLIs
args = ARGV.select { |arg| arg.start_with?("-") }
parser.order!(into: options)
puts "Options: #{options}"
puts "Remaining: #{ARGV}"
```ruby
```bash
$ ruby myapp.rb --name Alice git push
Options: {:name=>"Alice"}
Remaining: ["git", "push"]
```ruby
## Custom Type Coercion
For types beyond the built-in converters, you can define your own using `accept`. This is useful when you want to parse a custom format or validate input during parsing.
```ruby
require 'optparse'
Range = Struct.new(:min, :max)
options = {}
parser = OptionParser.new do |p|
p.accept(Range) do |str|
min, max = str.split("-").map(&:to_i)
Range.new(min, max)
end
p.on("--range RANGE", Range, "A range like 1-10") do |range|
options[:range] = range
end
end
parser.parse!(into: options)
p options
```ruby
```bash
$ ruby myapp.rb --range 5-20
{:range=>#<struct Range min=5, max=20>}
```ruby
## Handling Parse Errors
When something goes wrong, OptionParser raises specific exceptions. You can catch these to provide a clear error message instead of a raw stack trace.
```ruby
require 'optparse'
options = {}
parser = OptionParser.new do |p|
p.on("-p", "--port PORT", Integer, "Port number")
p.on("-h", "--help", "Show help") do
puts p
exit
end
end
begin
parser.parse!
rescue OptionParser::InvalidOption => e
puts "Unknown option: #{e.message}"
exit 1
rescue OptionParser::MissingArgument => e
puts "Missing value for #{e.message}"
exit 1
rescue OptionParser::InvalidArgument => e
puts "Invalid value: #{e.message}"
exit 1
end
```ruby
```bash
$ ruby myapp.rb -p hello
Invalid value: invalid number: hello
```ruby
## A Complete Example
Putting it all together, here is a greeting script with named greetings, a count option, verbose mode, and help text.
```ruby
#!/usr/bin/env ruby
require 'optparse'
options = {
name: "world",
count: 1,
verbose: false
}
OptionParser.new do |parser|
parser.banner = "Usage: greet.rb [options]"
parser.on("-n", "--name NAME", String, "Name to greet") do |name|
options[:name] = name
end
parser.on("-c", "--count N", Integer, "Number of greetings") do |n|
options[:count] = n
end
parser.on("-v", "--[no-]verbose", "Enable verbose mode") do |v|
options[:verbose] = v
end
parser.on("-h", "--help", "Show this help") do
puts parser
exit
end
end.parse!
options[:count].times do
puts "Hello, #{options[:name]}!"
puts "(running in verbose mode)" if options[:verbose]
end
```ruby
```bash
$ ruby greet.rb --help
Usage: greet.rb [options]
-n, --name NAME Name to greet
-c, --count N Number of greetings
-v, --[no-]verbose Enable verbose mode
-h, --help Show this help
$ ruby greet.rb -n Alice -c 3 -v
Hello, Alice!
(running in verbose mode)
Hello, Alice!
(running in verbose mode)
Hello, Alice!
(running in verbose mode)
```ruby
## See Also
- [Ruby Working with Strings](ruby-working-with-strings) — strings come up constantly in CLI work for formatting output and parsing arguments
- [Ruby Regular Expressions](ruby-regular-expressions) — when OptionParser isn't enough for complex argument patterns, regex fills the gaps
- [Ruby Blocks, Procs, and Lambdas](ruby-blocks-procs-lambdas) — understanding closures helps when writing custom OptionParser blocks and coercions