Parse command line arguments in a Ruby script
Ruby's built-in OptionParser does this nicely. Combine it with OpenStruct and you're home free:
require 'optparse'options = {}OptionParser.new do |opt| opt.on('--first_name FIRSTNAME') { |o| options[:first_name] = o } opt.on('--last_name LASTNAME') { |o| options[:last_name] = o }end.parse!puts options
options
will contain the parameters and values as a hash.
Saving and running that at the command line with no parameters results in:
$ ruby test.rb{}
Running it with parameters:
$ ruby test.rb --first_name=foo --last_name=bar{:first_name=>"foo", :last_name=>"bar"}
That example is using a Hash to contain the options, but you can use an OpenStruct which will result in usage like your request:
require 'optparse'require 'ostruct'options = OpenStruct.newOptionParser.new do |opt| opt.on('-f', '--first_name FIRSTNAME', 'The first name') { |o| options.first_name = o } opt.on('-l', '--last_name LASTNAME', 'The last name') { |o| options.last_name = o }end.parse!puts options.first_name + ' ' + options.last_name$ ruby test.rb --first_name=foo --last_name=barfoo bar
It even automatically creates your -h
or --help
option:
$ ruby test.rb -hUsage: test [options] --first_name FIRSTNAME --last_name LASTNAME
You can use short flags too:
require 'optparse'options = {}OptionParser.new do |opt| opt.on('-f', '--first_name FIRSTNAME') { |o| options[:first_name] = o } opt.on('-l', '--last_name LASTNAME') { |o| options[:last_name] = o }end.parse!puts options
Running that through its paces:
$ ruby test.rb -hUsage: test [options] -f, --first_name FIRSTNAME -l, --last_name LASTNAME$ ruby test.rb -f foo --l bar{:first_name=>"foo", :last_name=>"bar"}
It's easy to add inline explanations for the options too:
OptionParser.new do |opt| opt.on('-f', '--first_name FIRSTNAME', 'The first name') { |o| options[:first_name] = o } opt.on('-l', '--last_name LASTNAME', 'The last name') { |o| options[:last_name] = o }end.parse!
and:
$ ruby test.rb -hUsage: test [options] -f, --first_name FIRSTNAME The first name -l, --last_name LASTNAME The last name
OptionParser also supports converting the parameter to a type, such as an Integer or an Array. Refer to the documentation for more examples and information.
You should also look at the related questions list to the right:
Based on the answer by @MartinCortez here's a short one-off that makes a hash of key/value pairs, where the values must be joined with an =
sign. It also supports flag arguments without values:
args = Hash[ ARGV.join(' ').scan(/--?([^=\s]+)(?:=(\S+))?/) ]
…or alternatively…
args = Hash[ ARGV.flat_map{|s| s.scan(/--?([^=\s]+)(?:=(\S+))?/) } ]
Called with -x=foo -h --jim=jam
it returns {"x"=>"foo", "h"=>nil, "jim"=>"jam"}
so you can do things like:
puts args['jim'] if args.key?('h')#=> jam
While there are multiple libraries to handle this—including GetoptLong
included with Ruby—I personally prefer to roll my own. Here's the pattern I use, which makes it reasonably generic, not tied to a specific usage format, and flexible enough to allow intermixed flags, options, and required arguments in various orders:
USAGE = <<ENDUSAGEUsage: docubot [-h] [-v] [create [-s shell] [-f]] directory [-w writer] [-o output_file] [-n] [-l log_file]ENDUSAGEHELP = <<ENDHELP -h, --help Show this help. -v, --version Show the version number (#{DocuBot::VERSION}). create Create a starter directory filled with example files; also copies the template for easy modification, if desired. -s, --shell The shell to copy from. Available shells: #{DocuBot::SHELLS.join(', ')} -f, --force Force create over an existing directory, deleting any existing files. -w, --writer The output type to create [Defaults to 'chm'] Available writers: #{DocuBot::Writer::INSTALLED_WRITERS.join(', ')} -o, --output The file or folder (depending on the writer) to create. [Default value depends on the writer chosen.] -n, --nopreview Disable automatic preview of .chm. -l, --logfile Specify the filename to log to.ENDHELPARGS = { :shell=>'default', :writer=>'chm' } # Setting default valuesUNFLAGGED_ARGS = [ :directory ] # Bare arguments (no flag)next_arg = UNFLAGGED_ARGS.firstARGV.each do |arg| case arg when '-h','--help' then ARGS[:help] = true when 'create' then ARGS[:create] = true when '-f','--force' then ARGS[:force] = true when '-n','--nopreview' then ARGS[:nopreview] = true when '-v','--version' then ARGS[:version] = true when '-s','--shell' then next_arg = :shell when '-w','--writer' then next_arg = :writer when '-o','--output' then next_arg = :output when '-l','--logfile' then next_arg = :logfile else if next_arg ARGS[next_arg] = arg UNFLAGGED_ARGS.delete( next_arg ) end next_arg = UNFLAGGED_ARGS.first endendputs "DocuBot v#{DocuBot::VERSION}" if ARGS[:version]if ARGS[:help] or !ARGS[:directory] puts USAGE unless ARGS[:version] puts HELP if ARGS[:help] exitendif ARGS[:logfile] $stdout.reopen( ARGS[:logfile], "w" ) $stdout.sync = true $stderr.reopen( $stdout )end# etc.
I personally use Docopt. This is much more clear, maintainable and easy to read.
Have a look at the Ruby implementation's documentation for examples. The usage is really straightforward.
gem install docopt
Ruby code:
doc = <<DOCOPTMy program who says helloUsage: #{__FILE__} --first_name=<first_name> --last_name=<last_name>DOCOPTbegin args = Docopt::docopt(doc)rescue Docopt::Exit => e puts e.message exitendprint "Hello #{args['--first_name']} #{args['--last_name']}"
Then calling:
$ ./says_hello.rb --first_name=Homer --last_name=SimpsonsHello Homer Simpsons
And without arguments:
$ ./says_hello.rbUsage: says_hello.rb --first_name=<first_name> --last_name=<last_name>