Skip to Content
Course content

74: OptionParser for CLI Argument Parsing

Click on the "Edit" button in the top corner of the screen to edit your slide content.

When you first start writing scripts that take command-line arguments, you'll likely lean on ARGV. It's just an array of strings, right? It feels intuitive to just grab ARGV[0] for your filename and ARGV[1] for a setting. I've seen countless developers try to build "professional" CLI tools by manually shifting and popping elements off that array or writing a complex while loop to find specific flags.

"ARGV is enough for simple flags" vs. "OptionParser handles the edge cases for you"

Here is why manual ARGV parsing is a trap. Imagine you're building a tool to compress images. You want a flag for quality (-q) and a flag for the output directory (-o). You might write something like this:

# The "naive" way
quality = 80
output = "./dist"

if ARGV[0] == "-q"
  quality = ARGV[1].to_i
end
# ... and so on

This breaks the second a user decides to run ruby compress.rb -o ./backup -q 50 instead of -q 50 -o ./backup. Suddenly, your ARGV[0] is -o, and your quality logic fails. You could write a loop to search the array, but then you have to handle cases where a user provides --quality instead of -q, or forgets the value entirely. It's a rabbit hole of if/else statements that distracts you from the actual logic of your program.

Ruby's OptionParser library solves this by letting you define the rules for your arguments first, and then letting the library figure out where those arguments are hiding in the ARGV array, regardless of their order.

"Parsing is a manual loop" vs. "Defining options as a declarative block"

Instead of hunting through an array, you describe what your CLI expects. Let's build a hypothetical tool that processes a CSV file, allowing the user to specify a delimiter and whether they want a "verbose" output.

require 'optparse'

options = {}

# OptionParser.new takes a block where we define our flags
OptionParser.new do |opts|
  opts.banner = "Usage: csv_tool.rb [options] file.csv"

  # A simple switch (boolean)
  opts.on("-v", "--verbose", "Run verbosely") do |v|
    options[:verbose] = v
  end

  # An option that requires an argument
  # The "DELIM" string here is just a placeholder for the help menu
  opts.on("-d", "--delimiter DELIM", "Specify the CSV delimiter") do |d|
    options[:delimiter] = d
  end
end.parse! # The bang version modifies ARGV in place

# Now we can see what's left in ARGV
filename = ARGV[0]

puts "Processing #{filename} with delimiter #{options[:delimiter] || ','} (Verbose: #{!!options[:verbose]})"

Notice the parse! method. This is the "magic" bit. It iterates through ARGV, finds the flags you defined, executes their associated blocks, and—crucially—removes those flags from ARGV. By the time parse! finishes, ARGV only contains the positional arguments (like the filename), making your life significantly easier.

"Hardcoding help messages" vs. "Automatic help generation"

One of the most annoying parts of building a CLI is writing the --help menu. You update a feature, but forget to update the help text, and suddenly your documentation is lying to your users. I've been there, and it's a great way to get a grumpy bug report.

With OptionParser, the help menu is a byproduct of your definitions. Because we provided a banner and descriptive strings in the opts.on calls, OptionParser automatically handles the -h and --help flags for you. If you run the script above with ruby csv_tool.rb --help, it will print a perfectly formatted manual without you writing a single puts statement for the documentation. It's clean, it's maintainable, and it's the standard way to do things in the Ruby ecosystem.




📋 Practical Task

Build a System Log Filter Tool

Your task is to create a script called log_filter.rb that allows a user to filter a log file based on a severity level and a maximum number of lines to return.

The script should implement the following requirements using OptionParser:

  • A --level (or -l) option that takes a string (e.g., "ERROR", "WARN", "INFO"). This should be stored in an options hash.
  • A --limit (or -n) option that takes an integer. Ensure you convert the input to an integer using .to_i inside the block.
  • A --silent (or -s) switch that, when present, sets a boolean in your options hash.
  • The script must accept one positional argument: the path to the log file.

Expected behavior:
If the user runs ruby log_filter.rb -l ERROR -n 10 system.log, your script should print:
Filtering system.log for ERROR level, limiting to 10 results. Silent mode: false

Ensure that if no filename is provided as a positional argument, the script prints an error message and exits.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.