Command Line Apps with OptionParse

Share this article

Vector Command Line Icon

Command Line Applications may not be as common as they once were, but programmers use them just about every day. Even if you’re new to ruby you’ve probably used IRB and maybe some git (and if you haven’t yet, you will!) “Options”, “option flags” or just “flags” make interacting with these utilities faster, easier and more powerful, and Ruby is in on the fun.

When most people think of ruby they think of web apps and scripts. It’s true that ruby is awesome at both of these things, but you can use it to build command line utilities as well instead of using something like bash or C. Building the logic for options into your app from scratch every time would be a hassle, and fortunately, the standard ruby library includes a module called OptionParse that does the heavy lifting for us.

First let’s look at what an option is and what it does. “Options” and “arguments” look similar in a CLI but they are different. Here is a common git terminal command:

$ git commit -m "Initial commit"

In this example:

  • git is the application
  • commit is the command for the application to run
  • -m is the option flag
  • "Initial commit" is the argument passed to the command via the option

The above terminal command tells git to commit the currently staged files, and the -m flag let’s you type the commit message (notice how they used -m for “message”? clever!) all on one line. If you just type git commit into the terminal it will open an editor and ask for a commit message, along with some helper text. It looks something like this:

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
#
# Initial commit
#
# Changes to be committed:
# new file:   README.markdown

This is useful when you’re new to git. Chances are, however, that after you’ve made a few commits you don’t need to get flung into the editor and see this helper text time and time again. By using the -m option flag as we did above things are sped up considerably.

The OptionParse module makes it easy to create option flags and the helper text that explains to the user what each option does.

Because OptionParse is part of the standard library, you can add it to your project with

require 'optparse'

To show how the optparse library works, let’s take a look at a simple word manipulation tool I’ve written just for the occasion.

require 'optparse'

options = {}
OptionParser.new do |opts|
  opts.banner = "\nSimple word manipulator Ruby CLI app.\n"

  opts.on("-u", "--upcase", "Capitalize all letters") do |u|
    options[:upcase] = u
  end

  opts.on("-d", "--downcase", "Downcase all letters") do |d|
    options[:downcase] = d
  end

  opts.on("-c", "--capitalize", "Capitalize first character") do |c|
    options[:capitalize] = c
  end

  opts.on("-r", "--reverse", "Reverse the order of letters") do |r|
    options[:reverse] = r
  end

  opts.on("-h", "--help", "Displays help") do
    puts opts
    exit
  end
end.parse!

words = []

ARGV.each do |word|
  words << word.dup
end

words.each do |word|
  word.upcase! if options[:upcase]
  word.downcase! if options[:downcase]
  word.capitalize! if options[:capitalize]
  word.reverse! if options[:reverse]

  puts word
end

So, what does this code do? It takes arguments as strings from the command line, does one or more transformations, then puts the result to the terminal. Here’s what we get if we run this code with “Hello” and “World” as arguments, with the --reverse option:

$ ruby words.rb Hello World -r
#=> olleH
#=> dlroW

The first two lines don’t need much explanation: we require the optparse module and instantiate an empty hash called options.

require 'optparse'

options = {}

Jump past the OptionParse block and you see the ARGV global. This is where the arguments entered at the command line (but not the options, more on that in a second) are collected in an array. One special thing happening here: the arguments are all (#frozen)[http://www.ruby-doc.org/core-1.9.3/Object.html#method-i-freeze) which is why I’m copying the args into another array via the Object#dup method.

words = []

ARGV.each do |word|
  words << word.dup
end

The last block iterates over each word in the words array (a dup of the arguments entered at the command line), does the manipulations based on any options, and puts the result to the terminal.

words.each do |word|
  word.upcase! if options[:upcase]
  word.downcase! if options[:downcase]
  word.capitalize! if options[:capitalize]
  word.reverse! if options[:reverse]

  puts word
end

Finally, the magic. The code block from OptionParser.new do |opts| until end.parse! is where the options are defined, and at runtime, parsed from command line input. There are lots of conveniences built in to the OptionParser class:

  • OptionParser#banner lets you show arbitrary text to the user when they view help
  • OptionParser#on block lets you declare each of your options including the abbreviated flag (i.e. -u), the verbose flag (i.e. --upcase), the descriptive helper text (i.e. “Capitalize all letters”), and the behavior of the option
  • end.parse! intelligently pulls the option flags out of the argument list, leaving the actual arguments to be handled by your code. That means, if you need multiple flags, you can do any of the following and still get the same output:
    • $ ruby words.rb Hello World -r -u
    • $ ruby words.rb -r Hello World -u
    • $ ruby words.rb -ru Hello World

Let’s take one last look at the OptionParser.new block:

OptionParser.new do |opts|
  opts.banner = "\nSimple word manipulator Ruby CLI app."

  opts.on("-u", "--upcase", "Capitalize all letters") do |u|
    options[:upcase] = u
  end

  opts.on("-d", "--downcase", "Downcase all letters") do |d|
    options[:downcase] = d
  end

  opts.on("-c", "--capitalize", "Capitalize first character") do |c|
    options[:capitalize] = c
  end

  opts.on("-r", "--reverse", "Reverse the order of letters") do |r|
    options[:reverse] = r
  end

  opts.on("-h", "--help", "Displays help") do
    puts opts
    exit
  end
end.parse!

In our example, each option is just being used as a boolean, which is a common usage, but you could do pretty much anything you want. In a simple app I wrote for a previous job, most of my option flags added or removed file extensions from a global array before the rest of the code was executed.

This example command line app works (copy and paste it into your favorite text editor and try it out!), but it can be improved. For one thing, certain flags cannot be run together because of the order in which they are checked or the way the methods change the string. How would you change this code to make it more robust and more flexible? What happens if you have strings with special characters? How many arguments can you send before the code blows up (and, if there is a limit, are you likely to reach it hand typing into the terminal?) Let me know in the comments!

Frequently Asked Questions (FAQs) about Command Line Apps with OptionParse

What is OptionParse and why is it used in Ruby?

OptionParse is a class in Ruby that is used for command-line option analysis. It is a more advanced and flexible tool compared to the traditional ‘gets’ method. OptionParse allows you to specify the options your program will accept, automatically generates help and usage messages, and also ensures that the user has provided the correct arguments. It is a powerful tool for creating command-line applications in Ruby.

How do I create an OptionParser object?

To create an OptionParser object, you simply need to call the OptionParser.new method. This will create a new instance of the OptionParser class. You can then define the options for your program within the block passed to the new method. Here’s a simple example:

options = {}
OptionParser.new do |opts|
opts.banner = "Usage: example.rb [options]"
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
options[:verbose] = v
end
end.parse!

How do I handle different types of options with OptionParser?

OptionParser allows you to handle different types of options, including boolean flags, required arguments, and optional arguments. For boolean flags, you can use the ‘–[no-]’ prefix. For required arguments, you can use the on method with a mandatory argument. For optional arguments, you can use the on method with an optional argument.

How do I display a help message with OptionParser?

OptionParser automatically generates a help message based on the options you have defined. You can display this help message by calling the ‘help’ method on your OptionParser object. This is typically done when the user provides an invalid option or the ‘–help’ flag.

How do I handle errors with OptionParser?

OptionParser raises an OptionParser::InvalidOption exception when it encounters an invalid option. You can handle this exception using a rescue block. This allows you to display a custom error message and the automatically generated help message.

How do I parse command-line arguments with OptionParser?

To parse command-line arguments with OptionParser, you can use the ‘parse!’ method. This method modifies the ARGV array in place, removing any options and their arguments and leaving only the remaining non-option arguments.

Can I use OptionParser with subcommands?

Yes, OptionParser can be used with subcommands. You can define a separate OptionParser object for each subcommand, allowing each subcommand to have its own set of options.

How do I specify default values for options with OptionParser?

You can specify default values for options by setting the corresponding values in your options hash before calling OptionParser.new. If the user does not provide a value for an option, the default value will be used.

Can I use OptionParser with non-option arguments?

Yes, OptionParser can be used with non-option arguments. Any arguments that are not options (i.e., they do not start with a dash) are left in the ARGV array after ‘parse!’ is called.

How do I handle complex option parsing scenarios with OptionParser?

For complex option parsing scenarios, you can use the ‘order!’ method instead of ‘parse!’. The ‘order!’ method parses options in the order they appear, rather than in any order. This allows you to handle scenarios where different options need to be processed in a specific order.

David LyonsDavid Lyons
View Author

David is an Instructional Technologist happily hacking away in higher ed. When not behind a keyboard he can be found behind a controller, handlebars, or in the mountains.

Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week