Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parse command line arguments in a Ruby script

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.

like image 500
Don P Avatar asked Oct 17 '14 23:10

Don P


People also ask

How do I read command-line arguments in Ruby?

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.

How do I grab command-line arguments?

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.

What is argv in Ruby?

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.

What is argument parsing?

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.


2 Answers

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:

  • "Really Cheap Command-Line Option Parsing in Ruby"
  • "Pass variables to Ruby script via command line"
like image 156
the Tin Man Avatar answered Sep 20 '22 09:09

the Tin Man


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. 
like image 38
Phrogz Avatar answered Sep 19 '22 09:09

Phrogz