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