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 applicationcommit
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 helpOptionParser#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 optionend.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 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.