Command line option parsing in Ruby - ruby

I have a ruby script that parses command line options given to it as follows:
#!/usr/bin/ruby
require 'optparse'
puts 'Hello World!, This is my first ruby program'
options = {}
optparse = OptionParser.new do|opts|
opts.banner = "Featbuild minimal trial script for command line parsing"
options[:cpl] = nil
opts.on('-cpl SWITCH_STATE', 'compile on or off') do|cplopt|
options[:cpl] = cplopt
OPT_CPL=cplopt
puts cplopt
end
opts.on('-h', '--help', 'Display this screen') do
puts opts
exit
end
end
optparse.parse!
output = open("mypipe", "w+")
output.puts OPT_CPL
#output.flush
Now the line opts.on('-cpl SWITCH_STATE', 'compile on or off') do|cplopt| in the above script is where I have a problem.
I believe we can do it in follwoing ways:
1.)opts.on('--cpl SWITCH_STATE', 'compile on or off') do|cplopt|
2.)opts.on('-c', '--cpl SWITCH_STATE', 'compile on or off') do|cplopt|
3.)opts.on('-cpl SWITCH_STATE', 'compile on or off') do|cplopt|
This is what I pass as the arguments that works:
$./try1.rb --cpl on
$./try1.rb -c on
This does not work:
$./try1.rb -cpl on
Ruby, instead of getting 'on' as the option argument, gets 'pl', as if $./try.rb -c pl was specified.
I want to have the string $./try1.rb -cpl on be parsed in such a way that 'on' gets passed to the block of the method opts.on() in 'cplopt'.
I was referring to this tutorial: http://ruby.about.com/od/advancedruby/a/optionparser2.htm
It seems '-cpl on' isn't possible in Ruby? Is this so?
What other alternatve solutions can I apply over here?

Try Trollop, as it makes option parsing life easier.
require 'trollop'
opts = Trollop::options do
version "compile 0.1.0"
banner "Usage: compile <option> - where [options] are:"
opt :cpl, "compile on or off", :type => :string, :default => "off"
end
puts opts.cpl
When run, results in:
$ ruby ./trollop.rb --cpl on
on
$ ruby ./trollop.rb --cpl off
off
$ ruby ./trollop.rb -c on
on
$ ruby ./trollop.rb -c off
off
$ ruby ./trollop.rb
off
Trollop 2.0 supports no- negation of boolean options which you might find easier than dealing with the on/off strings.
opt "cpl", "Compile", :default => true
When run, results in:
$ ruby trollop.rb --cpl
true
$ ruby trollop.rb --no-cpl
false

I think you will need to ensure that only cp1 is in the single quotes instead of
-cpl SWITCH_STATE
do
opts.on('-cpl', 'compile on or off') do|cplopt|
options[:cpl] = cplopt
OPT_CPL=cplopt
puts cplopt
end
Here is an example:
opts.on('-s', '--size 1G or 1024M', '1G or 1024M') do |s|
options[:size] = s;
end

Related

Parse multiple command line options in Ruby using OptionParser

I've just started using OptionParser for Ruby and I wanted to use flags that would use more than just one argument.
For instance, I would like to be able to run:
script --move src dst
Note how src and dst are not separated using a coma.
My initial idea was:
opts.on("-m src dst", "--move src dst ", "move file from SRCto DST") do |src|
# do something
end
But this is not working. I assume that this is not the right approach. But how could this be done?
The example under the "Complete Example" section of the OptionParser details how a list of items can be accepted.
Here is a sample program based on that example. The third parameter Array in opts.on indicates that input src, dst should be used to create an array. To run this sample, you need to do gem install trollop.
# test.rb
require 'optparse'
options = {}
OptionParser.new do |opt|
opt.on("-m src, dst", "--move src, dst", Array, "Move from src to dst") do |list|
options[:src] = list[0]
options[:dst] = list[1]
end
end.parse!
puts options # It's a hash of parsed options
Sample run:
> ruby test.rb -m from,to
{:src=>"src", :dst=>"dst"}
>ruby test.rb -h
Usage: test [options]
-m, --move src, dst Move from src to dst
The above script forces one to separate the options using comma.
As indicated by "Really Cheap Command-Line Option Parsing in Ruby", there seems to be a gem, trollop, that can be quite easy to use for command-line parsing.
A sample program based on Trollop is given below, which allows usage of spaces for specifying options with multiple values
# test.rb
require "trollop"
opts = Trollop::options do
banner "Command line parsing using Trollop"
opt :move, "--move src dst', Move from src to dst", :short => "-m", :long => "--move", :type => :strings
end
# An array of option values
p opts.move
Sample run:
>ruby test.rb -m hello world
["hello", "world"]
>ruby test.rb -h
Command line parsing using Trollop
-m, --move=<s+> '--move src dst', Move from src to dst
-h, --help Show this message
There is a subtle difference between the help output between the two approaches. Trollop produces help text where --move=<s+> does not indicate clearly that it needs accepts two values, so I had to repeat the command syntax description.
OptionParser doesn't support that; It could be patched to do so, but I'm not sure it's worth the trouble.
Consider this code:
require 'optparse'
options = {}
OptionParser.new do |opt|
opt.on('-m', '--move') { |o| options[:move] = o }
end.parse!
from_name, to_name = ARGV
puts "Should move: #{ options.key?(:move) }"
puts "From: #{ from_name }"
puts "To: #{ to_name }"
Saving it and running it with various combinations of the parameters returns:
> ruby test.rb --move from to
Should move: true
From: from
To: to
> ruby test.rb from to
Should move: false
From:
To:
If the code is supposed to move files by default then don't bother with the --move flag, simply use:
test.rb from to
and consider removing the OptionParser block entirely.
If the code is supposed to normally copy with the option to move, then --move becomes more sensible to act as a flag that moving is desired.
ruby test.rb --move from to
I'd have code that tests for options[:move] and run the code to move instead of copy at that point.
In either case, the filenames shouldn't be tied to the flag, they should be supplied separately and retrieved from ARGV after OptionParser has finished parsing the command-line and removing entries it's handled.

Optparse doesn't seem to return ARGV array. Argument Required Error

This is homework and I do not expect you to give me the complete answer.
I'm trying to parse a command line entry such as:
./apacheReport.rb -u testlog.txt
When I enter this:
./apacheReport.rb -u testlog.txt
I get:
Argument required
My code is:
require_relative 'CommonLog'
require 'optparse'
# puts ARGV.inspect
optparser = OptionParser.new
optU = false
optI = false
optS = false
optH = false
optparser.banner = "apacheReport.rb [options] filename"
optparser.parse!
rescue => m
puts m.message
puts optparser
exit
end
if ARGV.length < 1
puts "Argument required"
exit
end
userInputFile = ARGV[0]
userInputFile.to_s
file = CommonLog.new(userInputFile)
It should parse the leftover portion of the command into ARGV[0] then should store it as userInputFile and then create a CommonLog object using the file as the constructor. At that point I call the methods that were specified in the command.
It seems that for some reason my ARGV is not being returned. I'm not sure what the issue is.
Ruby's OptionParser is easy to use, but you have to puzzle through the documentation. Here's a little example that'd be useful for your code:
require 'optparse'
options = {}
OptionParser.new do |opt|
opt.on('-u', '--use_this FILE', 'Use this file') { |o| options[:use_this] = o }
end.parse!
options will contain the flags. In this case, if you pass in -u foo, options[:use_this] will be foo.
Save that and try running it without and with a parameter. Also try running it with just a -h flag.
You can search StackOverflow for more answers where I was dealing with OptionParser.
It's hard to tell what's wrong since you code doesn't seem to be working at the moment. The problem may be that the parse! method removes found options from ARGV. So when you write:
optparser.parse!
It removes your two parameters (-u testlog.txt) and this code always fails:
if ARGV.length < 1
puts "Argument required"
exit
end
Instead of looking at ARGV, you need to set up optparser correctly. Perhaps something like:
optparser = OptionParser.new do |opts|
opts.banner = "apacheReport.rb [options] filename"
opts.on("-u", "--u-short-for-this", "Whatever u stands for") do |u|
optU = u
end
end
Then optU will be true only if the user passed -u and the filename will be in ARGV[0].

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.
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"
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.
There is a number of command line arguments parsers in Ruby:
GetoptLong - Included in stdlib
OptionParser - No longer part of stdlib, since Ruby 3.0.0 converted to a separate optparse gem
slop
optimist
and many more...
Personally I'd choose slop or optimist, those are not part of standard Ruby installation.
gem install slop
But it offers simplicity and code readability. Assuming slightly more complex example with required arguments and default values:
require 'slop'
begin
opts = Slop.parse do |o|
o.int '-a', '--age', 'Current age', default: 42
o.string '-f', '--first_name', 'The first name', required: true
o.string '-l', '--last_name', 'The last name', required: true
o.bool '-v', '--verbose', 'verbose output', default: false
o.on '-h','--help', 'print the help' do
puts o
exit
end
end
p opts.to_hash
rescue Slop::Error => e
puts e.message
end
optimist formerly known as trollop, it's very easy to ready, with minimum boilerplate code:
gem install optimist
require 'optimist'
opts = Optimist::options do
opt :verbose, "verbose mode"
opt :first_name, "The first name", type: :string, required: true
opt :last_name, "The last name", type: :string, required: true
opt :age, "Current age", default: 42
end
p opts
Similar example using OptionParser:
#!/usr/bin/env ruby
require 'optparse'
require 'ostruct'
begin
options = OpenStruct.new
OptionParser.new do |opt|
opt.on('-a', '--age AGE', 'Current age') { |o| options.age = o }
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 }
opt.on('-v', '--verbose', 'Verbose output') { |o| options.verbose = true }
end.parse!
options[:age] = 42 if options[:age].nil?
raise OptionParser::MissingArgument.new('--first_name') if options[:first_name].nil?
raise OptionParser::MissingArgument.new('--last_name') if options[:last_name].nil?
options[:verbose] = false if options[:verbose].nil?
rescue OptionParser::ParseError => e
puts e.message
exit
end
GetoptLong parsing is even more complicated:
require 'getoptlong'
opts = GetoptLong.new(
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
[ '--first_name', '-f', GetoptLong::REQUIRED_ARGUMENT ],
[ '--last_name', '-l', GetoptLong::REQUIRED_ARGUMENT ],
[ '--age','-a', GetoptLong::OPTIONAL_ARGUMENT ],
[ '--verbose','-v', GetoptLong::OPTIONAL_ARGUMENT ]
)
begin
options = {}
options[:verbose] = false
options[:age] = 42
opts.each do |opt, arg|
case opt
when '--help'
puts <<-EOF
usage: ./getlongopts.rb [options]
-a, --age Current age
-f, --first_name The first name
-l, --last_name The last name
-v, --verbose verbose output
-h, --help print the help
EOF
when '--first_name'
options[:first_name] = arg
when '--last_name'
options[:last_name] = arg
when '--age'
options[:age] = arg.to_i
when '--verbose'
options[:verbose] = arg
else
puts "unknown option `#{opt}`"
exit 1
end
end
raise GetoptLong::MissingArgument.new('Missing argument --first_name') if options[:first_name].nil?
raise GetoptLong::MissingArgument.new('Missing argument --last_name') if options[:last_name].nil?
rescue GetoptLong::Error => e
puts e.message
exit
end
puts options
Command line arguments was never meant to be a rocket science task, spend your time on reading/writing more useful code :)
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 = <<DOCOPT
My program who says hello
Usage:
#{__FILE__} --first_name=<first_name> --last_name=<last_name>
DOCOPT
begin
args = Docopt::docopt(doc)
rescue Docopt::Exit => e
puts e.message
exit
end
print "Hello #{args['--first_name']} #{args['--last_name']}"
Then calling:
$ ./says_hello.rb --first_name=Homer --last_name=Simpsons
Hello Homer Simpsons
And without arguments:
$ ./says_hello.rb
Usage:
says_hello.rb --first_name=<first_name> --last_name=<last_name>
A bit of standard Ruby Regexp in myscript.rb:
args = {}
ARGV.each do |arg|
match = /--(?<key>.*?)=(?<value>.*)/.match(arg)
args[match[:key]] = match[:value] # e.g. args['first_name'] = 'donald'
end
puts args['first_name'] + ' ' + args['last_name']
And on the command line:
$ ruby script.rb --first_name=donald --last_name=knuth
Produces:
$ donald knuth
Here is a slight modification to #Phrogz excellent answer: this mod will allow you to pass a string with spaces in it.
args= Hash[ ARGV.join(' ').scan(/--?([^=\s]+)(?:="(.*?)"+)?/)]
In a command line pass the string like this:
ruby my_script.rb '--first="Boo Boo" --last="Bear"'
Or from another ruby script like this:
system('ruby my_script.rb \'--first="Boo Boo" --last="Bear"\'')
Results:
{"first"=>"Boo Boo", "last"=>"Bear"}
An improved version that handles arguments that are not options, arguments with a parameter, and -a as well as --a.
def parse(args)
parsed = {}
args.each do |arg|
match = /^-?-(?<key>.*?)(=(?<value>.*)|)$/.match(arg)
if match
parsed[match[:key].to_sym] = match[:value]
else
parsed[:text] = "#{parsed[:text]} #{arg}".strip
end
end
parsed
end

How do I use command-line options in Ruby?

How do I use command options in scripts?
I have a script that connects to a device, runs commands and prints the output. I would like to use a file for the hosts, and one for the user and passwd info. When I try to run the script I get these errors:
ruby threat_detection.rb --host_file=hosts --config_file=config
threat_detection.rb:61:in '<main>': needless argument: --host_file=hosts (OptionParser::NeedlessArgument)
ruby threat_detection.rb '--host_file=hosts' '--config_file=config'
threat_detection.rb:61:in '<main>': needless argument: --host_file=hosts (OptionParser::NeedlessArgument)
ruby threat_detection.rb --host_file-hosts --config_file-config
threat_detection.rb:61:in '<main>': invalid option: --host_file-hosts (OptionParser::InvalidOption)
This is the code:
options ={}
opt_parser = OptionParser.new do |opt|
opt.banner = 'Usage: opt_parser COMMAND [OPTIONS]'
opt.on('--host_file', 'I need hosts, put them here') do |host_file|
options[:host_file] == host_file
end
opt.on('--config_file', 'I need config info, put it here') do |config_file|
options[:config_file] == config_file
end
opt.on('-h', '--help', 'What your looking at') do |help|
options[:help] == help
puts opt
end
end
opt_parser.parse!
How do I make these options get read? This is my guess (based off python experience). I'm just looking for it to print the lines right now:
if :config_file == true
File.open(:config_file, r) do |params|
puts params
end
end
if :host_file == true
File.open(:host_file, r) do |host|
put host
end
end
For taking arguments you need to use the following formats
"--switch=MANDATORY" or "--switch MANDATORY" # mandatory argument
"--switch[=OPTIONAL]" # optional argument
"--switch" # no argument
You are currently using the third format, which is being interpreted as taking no argument. You should use the first or second.
Also worth noting you probably want to be doing an assignment instead of a comparison when a flag is on
You want this
options[:host_file] = host_file
not this
options[:host_file] == host_file

Ruby Options parser not reading command line options

Im trying to use the Ruby builtin options parser
I have this file
File parser.rb
#!/usr/bin/env ruby
require 'optparse'
require 'pp'
class parser
def initialize(args)
#options = Hash.new()
#op = OptionParser.new do |opts|
#options[:verbose] = false
opts.on('-v', '--verbose', 'Output more information') do
#options[:verbose] = true
end
#options[:quick] = false
opts.on( '-q', '--quick', 'Perform the task quickly' ) do
#options[:quick] = true
end
#options[:logfile] = nil
opts.on( '-l', '--logfile FILE', 'Write log to FILE' ) do|file|
#options[:logfile] = file
end
opts.on( '-h', '--help', 'Display this screen' ) do
puts opts
exit
end
#options[:sID] = "-1"
opts.on('-sID', '--senderID', 'Sender ID used by device') do |sID|
#options[:sID] = sID
end
#options[:rID] = "-1"
opts.on('-rID', '--receiverID', 'Receiver ID used by device') do |rID|
#options[:rID] = rID
end
#op.parse!
#op
end
def getOptionsHash
#options
end
then Im trying to use this class in the file below
#!/usr/bin/env ruby
# Setup Bundler
require 'rubygems'
require 'bundler/setup'
require_relative 'parser'
#Variables in the options hash in parser.rb
op = Parser.new(ARGV)
pp op.getOptionsHash()
when I run this on the command line without args it uses default values:
./push_test.rb
I get the following output:
{:verbose=>false,
:quick=>false,
:logfile=>nil,
:sID=>"-1",
:rID=>"-1",
}
when I run this on the command line with args:
./push_test.rb -sID "33"
I get the following output:
{:verbose=>false,
:quick=>false,
:logfile=>nil,
:sID=>"ID",
:rID=>"-1",
}
Why is the sID not being set to 33?
Can anyone help please?Ive tried to figure this out but cant make any headway
Seems the short switch has to be a single character -s
./push_test.rb -sID "33"
outputs:
{:verbose=>false, :quick=>false, :logfile=>nil, :sID=>"ID", :rID=>"-1" }
because everything after -s to the first white space will be assigned to :sID, in your case its the word "ID" that follows "-s", hence you are getting :sID =>"ID"
./push_test.rb -s "33" will do the trick.
From the OptParser docs:
Short style switch:: Specifies short style switch which takes a
mandatory, optional or no argument. It's a string of the following
form:
"-xMANDATORY"
"-x[OPTIONAL]"
"-x"
So at specifying switch -sID you define switch -s with argument named ID - something different than you were probably expecting.

Resources