I am using Ruby OptionParser but can not figure out how to get non-option arguments as two lists.
myscript --option-one --option-two file1 file2 -- file10 file11
Is there a way to get from OptionParser two lists of files separately?
[file1, file2]
[file10, file11]
I do not care which of them remains in ARGV, just want to have two lists separately to submit them to different processing.
My current solution is
adding a handler of -- as follows
opts.on('--', 'marks the beginning of a different list of files') do
ARGV.unshift(:separator)
end
this produces ARGV with the following content
[ file1, file2, :separator, file10, file11 ]
and then, outside of OptionParser and after parse! was called, I modify ARGV
list1 = ARGV.shift(ARGV.index(:separator))
ARGV.shift
Is there a more elegant way of accomplishing it?
You're not using OptionParser correctly. It has the ability to create arrays/lists for you, but you have to tell it what you want.
You can define two separate options that each take an array, or, you could define one that takes an array and the other comes from ARGV after OptionParser finishes its parse! pass.
require 'optparse'
options = {}
OptionParser.new do |opt|
opt.on('--foo PARM2,PARM2', Array, 'first file list') { |o| options[:foo] = o }
opt.on('--bar PARM2,PARM2', Array, 'second file list') { |o| options[:bar] = o }
end.parse!
puts options
Saving and running that:
ruby test.rb --foo a,b --bar c,d
{:foo=>["a", "b"], :bar=>["c", "d"]}
Or:
require 'optparse'
options = {}
OptionParser.new do |opt|
opt.on('--foo PARM2,PARM2', Array, 'first file list') { |o| options[:foo] = o }
end.parse!
puts options
puts 'ARGV contains: "%s"' % ARGV.join('", "')
Saving and running that:
ruby test.rb --foo a,b c d
{:foo=>["a", "b"]}
ARGV contains: "c", "d"
You don't need to define --. -- is handled by the shell, not the script. This is from man sh:
-- A -- signals the end of options and disables further option processing. Any arguments after the --
are treated as filenames and arguments. An argument of - is equivalent to --.
Related
I am using Slop in ruby to parse the input arguments:
slop_opts = Slop.parse(ARGV.map(&:strip)) do |o|
o.string '--test1', 'explain test1'
o.string '--test2', 'explain test2'
o.on '--help' do
puts o
exit
end
end
slop_opts.to_hash
And I need that test2 could include several options:
e.g.
ruby this_script.rb --test1 one_arg --test2 first_arg second_arg
One constraint that I have is that I need first_arg and second_arg to be 2 different inputs, so I can't just get them from splitting on , (or similar) an input string like first_arg,second_arg.
Thank you for your help!
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
I am trying to write a script that get some arguments where some of them might be empty.
It seems that Ruby's OptionParser is not allowing that and throws (OptionParser::InvalidArgument).
Code:
require 'optparse'
options = {}
OptionParser.new do |opt|
opt.on('--might_be_empty might_be_empty', String) { |o| options[:might_be_empty] = o }
end.parse!
puts "might_be_empty: #{options[:might_be_empty]}"
Happy flow:
ruby ./for_stack.rb --might_be_empty "some_real_data"
might_be_empty: some_real_data
When the value is empty:
ruby ./for_stack.rb --might_be_empty ""
./for_stack.rb:10:in `<main>': invalid argument: --might_be_empty (OptionParser::InvalidArgument)
How can I tell the OptionParser to allow empty strings?
Leave coercion type unspecified, or use Object instead of String. Both behave the same.
opt.on('--might_be_empty might_be_empty') { ... }
# ..or
opt.on('--might_be_empty might_be_empty', Object) { ... }
Test:
ruby ./for_stack.rb --might_be_empty "some_real_data"
might_be_empty: some_real_data
ruby ./for_stack.rb --might_be_empty ""
might_be_empty:
According to the docs for OptionParser Type Coercion, passing String isn't just a "do nothing":
String – Any non-empty string
However, if you just leave the Argument pattern off of on (which directs you to the docs for make_switch):
Acceptable option argument format, must be pre-defined with #accept or #accept, or Regexp. This can appear once or assigned as String if not present, otherwise causes an ArgumentError.
While slightly confusing that it's "assigned as String if not present", it's not "assigned as a non-empty String if not present", and it will default to passing you any String, and work as you want it to:
opt.on('--might_be_empty might_be_empty') { |o| options[:might_be_empty] = o }
# is optional
% ruby example.rb
might_be_empty:
# if passed, must have a value
% ruby example.rb --might_be_empty
Traceback (most recent call last):
example.rb:8:in '<main>': missing argument: --might_be_empty (OptionParser::MissingArgument)
# can pass an empty string
% ruby example.rb --might_be_empty ""
might_be_empty:
# can pass any string
% ruby example.rb --might_be_empty "not empty"
might_be_empty: not empty
If you don't want to just leave the argument pattern off, you can create custom conversions, though this seems like overkill to me.
Option Parser allows optional values:
Running this several times:
require 'optparse'
options = {}
OptionParser.new do |opt|
opt.on('--might_be_empty [arg]') { |o| options[:might_be_empty] = o }
end.parse!
puts 'options[:might_be_empty].has_key? is %s' % options.has_key?(:might_be_empty)
puts 'options[:might_be_empty] is "%s"' % options[:might_be_empty]
puts 'options[:might_be_empty] is a %s' % options[:might_be_empty].class
pp ARGV
Shows me:
$ ruby test.rb -m
options[:might_be_empty].has_key? is true
options[:might_be_empty] is ""
options[:might_be_empty] is a NilClass
[]
$ ruby test.rb -m foo
options[:might_be_empty].has_key? is true
options[:might_be_empty] is "foo"
options[:might_be_empty] is a String
[]
$ ruby test.rb -m 1
options[:might_be_empty].has_key? is true
options[:might_be_empty] is "1"
options[:might_be_empty] is a String
[]
This is documented a couple times in the sample code but not explicitly stated in the text:
def perform_inplace_option(parser)
# Specifies an optional option argument
parser.on("-i", "--inplace [EXTENSION]",
"Edit ARGV files in place",
"(make backup if EXTENSION supplied)") do |ext|
self.inplace = true
self.extension = ext || ''
self.extension.sub!(/\A\.?(?=.)/, ".") # Ensure extension begins with dot.
end
end
Also note that you don't have to coerce the returned value because it's already a String. Any values passed in from the command-line are strings as they're snatched from ARGV.
I have a command line tool which generates a flat json doc. Imagine this:
$ ruby gen-json.rb --foo bar --baz qux
{
"foo": "bar"
"baz": "qux"
}
What I want is this to work:
$ ruby gen-json.rb --foo $'\0' --baz qux
{
"foo": null,
"baz": "qux"
}
Instead of null I get an empty string. To simplify the problem even further consider this:
$ cat nil-args.rb
puts "argv[0] is nil" if ARGV[0].nil?
puts "argv[0] is an empty string" if ARGV[0] == ""
puts "argv[1] is nil" if ARGV[1].nil?
I want to run it like this and get this output:
$ ruby nil-args.rb $'\0' foo
argv[0] is nil
But instead I get
argv[0] is an empty string
I suspect this is (arguably) a bug in the ruby interpreter. It is treating argv[0] as a C string which null terminates.
Command line arguments are always strings. You will need to use a sentinel to indicate arguments you want to treat otherwise.
I'm pretty sure you literally cannot do what you are proposing. It's a fundamental limitation of the shell you are using. You can only ever pass string arguments into a script.
It has already been mentioned in a comment but the output you get with the \0 method you tried makes perfect sense. The null terminator technically is an empty string.
Also consider that accessing any element of an array that has not yet been defined will always be nil.
a = [1, 2, 3]
a[10].nil?
#=> true
A possible solution, however, would be for your program to work like this:
$ ruby gen-json.rb --foo --baz qux
{
"foo": null,
"baz": "qux"
}
So when you have a double minus sign argument followed by another double minus sign argument, you infer that the first one was null. You will need to write your own command line option parser to achieve this, though.
Here is a very very simple example script that seems to work (but likely has edge cases and other problems):
require 'pp'
json = {}
ARGV.each_cons(2) do |key, value|
next unless key.start_with? '--'
json[key] = value.start_with?('--') ? nil : value
end
pp json
Would that work for your purposes? :)
Ruby treats EVERYTHING except nil and false as true. Therefore, any empty string will evaluate as true (as well as 0 or 0.0).
You can force the nil behaviour by having something like this:
ARGV.map! do |arg|
if arg.empty?
nil
else
arg
end
end
this way, any empty strings will be transformed in a reference to nil.
You're going to have to decide on a special character to mean nil. I would suggest "\0" (double-quotes ensures the literal string backslash zero is passed in). You can then use a special type converter in OptionParser:
require 'optparse'
require 'json'
values = {}
parser = OptionParser.new do |opts|
opts.on("--foo VAL",:nulldetect) { |val| values[:foo] = val }
opts.on("--bar VAL",:nulldetect) { |val| values[:foo] = val }
opts.accept(:nulldetect) do |string|
if string == '\0'
nil
else
string
end
end
end
parser.parse!
puts values.to_json
Obviously, this requires you to be explicit about the flags you accept, but if you want to accept any flag, you can certainly just hand-jam the if statements for checking for "\0"
As a side note, you can have flags be optional, but you get the empty string passed to the block, e.g.
opts.on("--foo [VAL]") do |val|
# if --foo is passed w/out args, val is empty string
end
So, you'd still need a stand-in
n00b question alert!
here is the problem:
I am creating a shell script that takes a minimum of 3 arguments: a string, a line number, and at least one file.
I've written a script that will accept EXACTLY 3 arguments, but I don't know how to handle multiple file name arguments.
here's the relevant parts of my code (skipping the writing back into the file etc):
#!/usr/bin/env ruby
the_string = ARGV[0]
line_number = ARGV[1]
the_file = ARGV[2]
def insert_script(str, line_n, file)
f = file
s = str
ln = line_n.to_i
if (File.file? f)
read_in(f,ln,s)
else
puts "false"
end
end
def read_in(f,ln,s)
lines = File.readlines(f)
lines[ln] = s + "\n"
return lines
end
# run it
puts insert_script(the_string, line_number, the_file)
now I know that it's easy to write a block that will iterate through ALL the arguments:
ARGV.each do |a|
puts a
end
but I need to ONLY loop through the args from ARGV[2] (the first file name) to the last file name.
I know there's got to be - at a minimum - at least one easy way to do this, but I just can't see what it is at the moment!
in any case - I'd be more than happy if someone can just point me to a tutorial or an example, I'm sure there are plenty out there - but I can't seem to find them.
thanks
Would you consider using a helpful gem? Trollop is great for command line parsing because it automatically gives you help messages, long and short command-line switches, etc.
require 'trollop'
opts = Trollop::options do
opt :string, "The string", :type => :string
opt :line, "line number", :type => :int
opt :file, "file(s)", :type => :strings
end
p opts
When I call it "commandline.rb" and run it:
$ ruby commandline.rb --string "foo bar" --line 3 --file foo.txt bar.txt
{:string=>"foo bar", :line=>3, :file=>["foo.txt", "bar.txt"], :help=>false, :string_given=>true, :line_given=>true, :file_given=>true}
If you modify the ARGV array to remove the elements you're no longer interested in treating as filenames, you can treat all remaining elements as filenames and iterate over their contents with ARGF.
That's a mouthful, a small example will demonstrate it more easily:
argf.rb:
#!/usr/bin/ruby
str = ARGV.shift
line = ARGV.shift
ARGF.each do |f|
puts f
end
$ ./argf.rb one two argf.rb argf.rb
#!/usr/bin/ruby
str = ARGV.shift
line = ARGV.shift
ARGF.each do |f|
puts f
end
#!/usr/bin/ruby
str = ARGV.shift
line = ARGV.shift
ARGF.each do |f|
puts f
end
$
There are two copies of the argf.rb file printed to the console because I gave the filename argf.rb twice on the command line. It was opened and iterated over once for each mention.
If you want to operate on the files as files, rather than read their contents, you can simply modify the ARGV array and then use the remaining elements directly.
The canonical way is to use shift, like so:
the_string = ARGV.shift
line_number = ARGV.shift
ARGV.each do |file|
puts insert_script(the_string, line_number, the_file)
end
Take a look at OptionParser - http://ruby-doc.org/stdlib-1.9.3/libdoc/optparse/rdoc/OptionParser.html. It allows you to specify the number of arguments, whether they are mandatory or optional, handle errors such as MissingArgument or InvalidOption.
An alternate (and somewhat uglier) trick if you don't want to use another library or change the ARGV array is to use .upto
2.upto(ARGV.length-1) do |i|
puts ARGV[i]
end
I don't know ruby very well, but I'm trying to add some functionality to this script a co-worker wrote.
Basically right now it takes a few flags and standard in as input, and it uses OptionParser to parse the flags.
I want to use OptionParser to parse a selection of command line arguments similar to those of cat. So I guess my question is how would I write the command line options parsing part of cat in ruby using OptionParser
cat [OPTION]... [FILE]...
Hope that makes sense, any help is appreciated.
OPTS = {}
op = OptionParser.new do |x|
x.banner = 'cat <options> <file>'
x.separator ''
x.on("-A", "--show-all", "Equivalent to -vET")
{ OPTS[:showall] = true }
x.on("-b", "--number-nonblank", "number nonempty output lines")
{ OPTS[:number_nonblank] = true }
x.on("-x", "--start-from NUM", Integer, "Start numbering from NUM")
{ |n| OPTS[:start_num] = n }
x.on("-h", "--help", "Show this message")
{ puts op; exit }
end
op.parse!(ARGV)
# Example code for dealing with filenames
ARGV.each{ |fn| output_file(OPTS, fn) }
I shall leave other command line operations, as they say, as an exercise for the reader! You get the idea.
(NB: I had to invent a fictional -x parameter to demo passing a value after a flag.)
Update: I should have explained that this will leave ARGV as an array of filenames, assuming that the user has entered any.