Parse Command Line Arguments in a Ruby Script

Parse command line arguments in a Ruby script

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.

Multicharacter command-line arguments with a ruby script

Slop will work for me. Thanks to Boris Pilgun

Really Cheap Command-Line Option Parsing in Ruby

Here's the standard technique I usually use:

#!/usr/bin/env ruby

def usage(s)
$stderr.puts(s)
$stderr.puts("Usage: #{File.basename($0)}: [-l <logfile] [-q] file ...")
exit(2)
end

$quiet = false
$logfile = nil

loop { case ARGV[0]
when '-q' then ARGV.shift; $quiet = true
when '-l' then ARGV.shift; $logfile = ARGV.shift
when /^-/ then usage("Unknown option: #{ARGV[0].inspect}")
else break
end; }

# Program carries on here.
puts("quiet: #{$quiet} logfile: #{$logfile.inspect} args: #{ARGV.inspect}")

Pass variables to Ruby script via command line

Something like this:

ARGV.each do|a|
puts "Argument: #{a}"
end

then

$ ./test.rb "test1 test2"

or

v1 = ARGV[0]
v2 = ARGV[1]
puts v1 #prints test1
puts v2 #prints test2

Slop parsing command line arguments with multiple inputs per argument option in ruby

Make --test2 an Array argument. Set the delimiter to nil to disable splitting the input.

slop_opts = Slop.parse(ARGV.map(&:strip)) do |o|                                
o.string '--test1', 'explain test1'
o.array '--test2', 'explain test2', delimiter: nil
o.on '--help' do
puts o
exit
end
end

Then each input gets its own --test2.

ruby this_script.rb --test1 one_arg --test2 first_arg --test2 second_arg

Accepting Command-Line Arguments into a Ruby Script

defined?(ARGV) won't return a boolean false, but rather "constant". Since that doesn't evaluate to false, filename gets defined as ARGV[0], which is nil.

>> ARGV
=> []
>> defined?(ARGV)
=> "constant"
?> ARGV.first
=> nil

Instead you might check the length of ARGV:

if ARGV.length > 0
filename = ARGV.first.chomp
end

From the docs:

defined? expression tests whether or not expression refers to anything recognizable (literal object, local variable that has been initialized, method name visible from the current scope, etc.). The return value is nil if the expression cannot be resolved. Otherwise, the return value provides information about the expression.

Ruby example with exclamation mark

To answer your questions:

I can understand the exclamation mark on the "end.parse" line (but I expected a parameter after that)

The documentation states that parse! takes an optional argv parameter. If it is not supplied, it defaults to default_argv, which I imagine is the string containing the arguments passed to this script in the command line.

p hasn't been declared, and I can't understand if it's part of the example source

p is defined in Kernel, so it is (almost) always available in Ruby. p obj is equivalent to puts obj.inspect.

In this context, p is just used to illustrate that after parsing the arguments, the options hash contains all the flags/options you defined in the OptionParser block.

And how do I use the '-v' option? Do I simply check if options[:v] is nil or true?

Yes, but that would actually be options[:verbose].

Last thing, what happens to the other options? Does the OptionParser only parse switches? What if I had other parameters after the '-v'? Like myscript -v duck ketchup banana?

You will have to make multiple calls to opts.on to match all the other switches/arguments you are interested in. Look at the documentation here for explanations on how to do that.



Related Topics



Leave a reply



Submit