40

I need to parse a command line like

  script.rb <mandatory filename> [options]

with optparse.

Sure I can write some custom code to handle the filename, then pass ARGV to optparse, but maybe there's a simpler way to do it?

EDIT: there's another hacky way to parse such a command line, and that is pass ['--mandatory-filename'] + ARGV to optparse, then handle the --mandatory-filename option.

Leonid Shevtsov
  • 14,024
  • 9
  • 51
  • 82
  • See below answers, definitely; however, I would recommend you swapping your `mandatory filename` and `options` parameters. Usually, the non-switch arguments come last on a command-line, unless you have a particular reason to do otherwise – Travis Warlick Dec 27 '11 at 13:24

5 Answers5

54

First parse! with optparse, then scan the ARGV and raise if ARGV is empty. Like so:

op.parse!
filename = ARGV.pop
raise "Need to specify a file to process" unless filename

The mandatory filename will not be processed by the OptionParser and will be left for you in ARGV - if it's not there, just raise manually.

Julik
  • 7,676
  • 2
  • 34
  • 48
  • 1
    Use something like `op.banner += ' '` to adjust the `Usage:` line accordingly. – sschuberth Sep 30 '15 at 15:12
  • 1
    wouldn't `ARGV.shift` instead of `ARGV.pop` be better if we wanted to expand to multiple required params? – nhed Apr 10 '17 at 01:28
  • nhed, yeah good idea since first one or two positional arguments are usually required and latter ones are optional (like how with `zip` you first have to put the filename for the archive, and then a list of files that get zipped into the archive). – Greg Schmit Dec 27 '17 at 19:31
13

Just to follow up on what Julik and Shadowfirebird said: When parsing with OptionParser be aware that parse! and parse have different functionality. The former will remove every argument it understands out of the passed array where the latter will leave them be. This changes your conditions for determining if the required argument is present.

Julik
  • 7,676
  • 2
  • 34
  • 48
Geoff
  • 151
  • 2
11

Although it doesn't apply to every situation, it is often nice to be able to process multiple files on a single command line, such as:

script.rb [options] file1 file2 ...

file1 is mandatory, but file2 and beyond is optional.

The best way I know to do this follows this convention:

options = {}
optparse = OptionParser.new do |opts|
  opts.banner = "Usage: script.rb [options] file1 file2 ..."

  opts.on('-a', '--an-option ARG', 'Set some option') do |arg|
    options[:a] = arg
  end

  ...
end
optparse.parse!

# Check required conditions
if ARGV.empty?
  puts optparse
  exit(-1)
end

If files are not provided, a help message will be displayed with the usage banner and a description of options. If the files are present, they will be the only thing left in ARGV.

adamlamar
  • 4,629
  • 2
  • 27
  • 22
3

I am not sure if it was added recently, but none of the previous answers mention that optparse.parse will return the ARGV value after removing the parsed options.

If you do this:

rest = optparse.parse!

You will get an array with the given file/s (along unknown options). This way you do not have to care if the options come before or after the file.

SystematicFrank
  • 16,555
  • 7
  • 56
  • 102
0

Optparse only does arguments with parameters, AFAIK. The "correct" way to handle your filename is to deal with it outside of optparse. I posted some example code for this in answer to this question.

BTW, that's a rather unusual commandline. If it's just for you, fine, but others are likely to find it rather counter-intuitive. It would be more normal to have: script.rb [options] <filename>...

Community
  • 1
  • 1
Shadowfirebird
  • 757
  • 3
  • 13
  • OptParse handles all sorts of options. It does flags, parameters, optional parameters, lists, booleans. See the [Complete example](http://ruby-doc.org/ruby-1.9/classes/OptionParser.html) for more info. – the Tin Man Nov 24 '10 at 22:27