I want to call a Ruby script from the command line, and pass in parameters that are key/value pairs.
Command line call:
$ ruby my_script.rb --first_name=donald --last_name=knuth
my_script.rb:
puts args.first_name + args.last_name
What is the standard Ruby way to do this? In other languages I usually have to use an option parser. In Ruby I saw we have ARGF.read
, but that does not seem to work key/value pairs like in this example.
OptionParser looks promising, but I can't tell if it actually supports this case.
In your Ruby programs, you can access any command-line arguments passed by the shell with the ARGV special variable. ARGV is an Array variable which holds, as strings, each argument passed by the shell.
To pass command line arguments, we typically define main() with two arguments : first argument is the number of command line arguments and second is list of command-line arguments. The value of argc should be non negative. argv(ARGument Vector) is array of character pointers listing all the arguments.
ARGV in Ruby In Ruby, ARGV is a constant defined in the Object class. It is an instance of the Array class and has access to all the array methods. Since it's an array, even though it's a constant, its elements can be modified and cleared with no trouble.
Argument Parsing using sys.Your program will accept an arbitrary number of arguments passed from the command-line (or terminal) while getting executed. The program will print out the arguments that were passed and the total number of arguments.
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.new 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! puts options.first_name + ' ' + options.last_name $ ruby test.rb --first_name=foo --last_name=bar foo bar
It even automatically creates your -h
or --help
option:
$ ruby test.rb -h Usage: 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 -h Usage: 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 -h Usage: 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 = <<ENDUSAGE Usage: docubot [-h] [-v] [create [-s shell] [-f]] directory [-w writer] [-o output_file] [-n] [-l log_file] ENDUSAGE HELP = <<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. ENDHELP ARGS = { :shell=>'default', :writer=>'chm' } # Setting default values UNFLAGGED_ARGS = [ :directory ] # Bare arguments (no flag) next_arg = UNFLAGGED_ARGS.first ARGV.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 end end puts "DocuBot v#{DocuBot::VERSION}" if ARGS[:version] if ARGS[:help] or !ARGS[:directory] puts USAGE unless ARGS[:version] puts HELP if ARGS[:help] exit end if ARGS[:logfile] $stdout.reopen( ARGS[:logfile], "w" ) $stdout.sync = true $stderr.reopen( $stdout ) end # etc.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With