Really Cheap Command-Line Option Parsing in Ruby

Ruby

Ruby Problem Overview


EDIT: Please, please, please read the two requirements listed at the bottom of this post before replying. People keep posting their new gems and libraries and whatnot, which clearly don't meet the requirements.

Sometimes I want to very cheaply hack some command line options into a simple script. A fun way to do it, without dealing with getopts or parsing or anything like that, is:

...
$quiet       = ARGV.delete('-d')
$interactive = ARGV.delete('-i')
...
# Deal with ARGV as usual here, maybe using ARGF or whatever.

It's not quite the normal Unix options syntax, because it will accept options non-option command line parameters, as in "myprog -i foo bar -q", but I can live with that. (Some people, such as the Subversion developers, prefer this. Sometimes I do too.)

An option that's just present or absent can't be implemented much more simply than the above. (One assignment, one function call, one side effect.) Is there an equally simple way to deal with options that take a parameter, such as "-f filename"?

EDIT:

One point I didn't make earlier on, because it hadn't become clear to me until the author of Trollop mentioned that the library fit "in one [800-line] file," is that I'm looking not only for clean syntax, but for a technique that has the following characteristics:

  1. The entirety of the code can be included in the script file (without overwhelming the actual script itself, which may be only a couple of dozen lines), so that one can drop a single file in a bin dir on any system with a standard Ruby 1.8.[5-7] installation and use it. If you can't write a Ruby script that has no require statements and where the code to parse a couple of options is under a dozen lines or so, you fail this requirement.

  2. The code is small and simple enough that one can remember enough of it to directly type in code that will do the trick, rather than cutting and pasting from somewhere else. Think of the situation where you're on the console of a firewalled sever with no Internet access, and you want to toss together a quick script for a client to use. I don't know about you, but (besides failing the requirement above) memorizing even the 45 lines of simplified micro-optparse is not something I care to do.

Ruby Solutions


Solution 1 - Ruby

As the author of Trollop, I cannot BELIEVE the stuff that people think is reasonable in an option parser. Seriously. It boggles the mind.

Why should I have to make a module that extends some other module to parse options? Why should I have to subclass anything? Why should I have to subscribe to some "framework" just to parse the command line?

Here's the Trollop version of the above:

opts = Trollop::options do
  opt :quiet, "Use minimal output", :short => 'q'
  opt :interactive, "Be interactive"
  opt :filename, "File to process", :type => String
end

And that's it. opts is now a hash with keys :quiet, :interactive, and :filename. You can do whatever you want with it. And you get a beautiful help page, formatted to fit your screen width, automatic short argument names, type checking... everything you need.

It's one file, so you can drop it in your lib/ directory if you don't want a formal dependency. It has a minimal DSL that is easy to pick up.

LOC per option people. It matters.

Solution 2 - Ruby

I share your distaste for require 'getopts', mainly due to the awesomeness that is OptionParser:

% cat temp.rb                                                            
require 'optparse'
OptionParser.new do |o|
  o.on('-d') { |b| $quiet = b }
  o.on('-i') { |b| $interactive = b }
  o.on('-f FILENAME') { |filename| $filename = filename }
  o.on('-h') { puts o; exit }
  o.parse!
end
p :quiet => $quiet, :interactive => $interactive, :filename => $filename
% ruby temp.rb                                                           
{:interactive=>nil, :filename=>nil, :quiet=>nil}
% ruby temp.rb -h                                                        
Usage: temp [options]
    -d
    -i
    -f FILENAME
    -h
% ruby temp.rb -d                                                        
{:interactive=>nil, :filename=>nil, :quiet=>true}
% ruby temp.rb -i                                                        
{:interactive=>true, :filename=>nil, :quiet=>nil}
% ruby temp.rb -di                                                       
{:interactive=>true, :filename=>nil, :quiet=>true}
% ruby temp.rb -dif apelad                                               
{:interactive=>true, :filename=>"apelad", :quiet=>true}
% ruby temp.rb -f apelad -i                                              
{:interactive=>true, :filename=>"apelad", :quiet=>nil}

Solution 3 - 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}")

Solution 4 - Ruby

Since nobody appeared to mention it, and the title does refer to cheap command-line parsing, why not just let the Ruby interpreter do the work for you? If you pass the -s switch (in your shebang, for example), you get dirt-simple switches for free, assigned to single-letter global variables. Here's your example using that switch:

#!/usr/bin/env ruby -s
puts "#$0: Quiet=#$q Interactive=#$i, ARGV=#{ARGV.inspect}"

And here's the output when I save that as ./test and chmod it +x:

$ ./test
./test: Quiet= Interactive=, ARGV=[]
$ ./test -q foo
./test: Quiet=true Interactive=, ARGV=["foo"]
$ ./test -q -i foo bar baz
./test: Quiet=true Interactive=true, ARGV=["foo", "bar", "baz"]
$ ./test -q=very foo
./test: Quiet=very Interactive=, ARGV=["foo"]

See ruby -h for details.

That must be as cheap as it gets. It will raise a NameError if you try a switch like -:, so there's some validation there. Of course, you can't have any switches after a non-switch argument, but if you need something fancy, you really should be using at the minimum OptionParser. In fact, the only thing that annoys me about this technique is that you'll get a warning (if you've enabled them) when accessing an unset global variable, but it's still falsey, so it works just fine for throwaway tools and quick scripts.

One caveat pointed out by FelipeC in the comments in "https://stackoverflow.com/questions/897630/really-cheap-command-line-option-parsing-in-ruby/20126651#comment83609042_20126651";, is that your shell might not support the 3-token shebang; you may need to replace /usr/bin/env ruby -w with the actual path to your ruby (like /usr/local/bin/ruby -w), or run it from a wrapper script, or something.

Solution 5 - Ruby

I built micro-optparse to fill this obvious need for a short, but easy to use option-parser. It has a syntax similar to Trollop and is 70 lines short. If you don't need validations and can do without empty lines you can cut it down to 45 lines. I think that's exactly what you were looking for.

Short example:

options = Parser.new do |p|
  p.version = "fancy script version 1.0"
  p.option :verbose, "turn on verbose mode"
  p.option :number_of_chairs, "defines how many chairs are in the classroom", :default => 1
  p.option :room_number, "select room number", :default => 2, :value_in_set => [1,2,3,4]
end.process!

Calling the script with -h or --help will print

Usage: micro-optparse-example [options]
    -v, --[no-]verbose               turn on verbose mode
    -n, --number-of-chairs 1         defines how many chairs are in the classroom
    -r, --room-number 2              select room number
    -h, --help                       Show this message
    -V, --version                    Print version

It checks if input is of same type as the default value, generates short and long accessors, prints descriptive error messages if invalid arguments are given and more.

I compared several option-parser by using each option-parser for the problem I had. You can use these examples and my summary to make an informative decision. Feel free to add more implementations to the list. :)

Solution 6 - Ruby

I totally understand why you want to avoid optparse - it can get too much. But there are a few far "lighter" solutions (compared to OptParse) that come as libraries but are simple enough to make a single gem installation worthwhile.

For example, check out this OptiFlag example. Just a few lines for the processing. A slightly truncated example tailored to your case:

require 'optiflag'

module Whatever extend OptiFlagSet
  flag "f"
  and_process!
end 

ARGV.flags.f # => .. whatever ..

There are tons of customized examples too. I recall using another that was even easier, but it has escaped me for now but I will come back and add a comment here if I find it.

Solution 7 - Ruby

This is what I use for really, really cheap args:

def main
  ARGV.each { |a| eval a }
end

main

so if you run programname foo bar it calls foo and then bar. It's handy for throwaway scripts.

Solution 8 - Ruby

You can try something like:

if( ARGV.include( '-f' ) )
  file = ARGV[ARGV.indexof( '-f' ) + 1 )]
  ARGV.delete('-f')
  ARGV.delete(file)
end

Solution 9 - Ruby

Have you considered Thor by wycats? I think it's a lot cleaner than optparse. If you already have a script written, it might be some more work to format it or refactor it for thor, but it does make handling options very simple.

Here's the example snippet from the README:

class MyApp < Thor                                                # [1]
  map "-L" => :list                                               # [2]

  desc "install APP_NAME", "install one of the available apps"    # [3]
  method_options :force => :boolean, :alias => :optional          # [4]
  def install(name)
    user_alias = options[:alias]
    if options.force?
      # do something
    end
    # ... other code ...
  end

  desc "list [SEARCH]", "list all of the available apps, limited by SEARCH"
  def list(search = "")
    # list everything
  end
end

Thor automatically maps commands as such:

app install myname --force

That gets converted to:

MyApp.new.install("myname")
# with {'force' => true} as options hash
  1. Inherit from Thor to turn a class into an option mapper
  2. Map additional non-valid identifiers to specific methods. In this case, convert -L to :list
  3. Describe the method immediately below. The first parameter is the usage information, and the second parameter is the description.
  4. Provide any additional options. These will be marshaled from -- and - params. In this case, a --force and a -f option is added.

Solution 10 - Ruby

Here's my favorite quick-and-dirty option parser:

case ARGV.join
when /-h/
  puts "help message"
  exit
when /-opt1/
  puts "running opt1"
end

The options are regular expressions, so "-h" also would match "--help".

Readable, easy to remember, no external library, and minimal code.

Solution 11 - Ruby

Trollop is pretty cheap.

Solution 12 - Ruby

If you want a simple command line parser for key/value commands without the use of gems:

But this only works if you always have key/value pairs.

# example
# script.rb -u username -p mypass

# check if there are even set of params given
if ARGV.count.odd? 
	puts 'invalid number of arguments'
	exit 1
end

# holds key/value pair of cl params {key1 => value1, key2 => valye2, ...}
opts = {} 

(ARGV.count/2).times do |i|
	k,v = ARGV.shift(2)
	opts[k] = v # create k/v pair
end

# set defaults if no params are given
opts['-u'] ||= 'root'

# example use of opts
puts "username:#{opts['-u']} password:#{opts['-p']}"

If you don't' need any checking you could just use:

opts = {} 

(ARGV.count/2).times do |i|
	k,v = ARGV.shift(2)
	opts[k] = v # create k/v pair
end

Solution 13 - Ruby

Here's the code snippet I use at the top of most of my scripts:

arghash = Hash.new.tap { |h| # Parse ARGV into a hash
    i = -1                      
    ARGV.map{  |s| /(-[a-zA-Z_-])?([^=]+)?(=)?(.+)?/m.match(s).to_a }
     .each{ |(_,a,b,c,d)| h[ a ? "#{a}#{b}#{c}" : (i+=1) ] =
                             (a ? (c ? "#{d}" : true) : "#{b}#{c}#{d}") 
          }
    [[:argc,Proc.new  {|| h.count{|(k,_)| !k.is_a?(String)}}],
     [:switches, Proc.new {|| h.keys.select{|k| k[0] == '-' }}]
    ].each{|(n,p)| h.define_singleton_method(n,&p) }
}

I also hate to require additional files in my quick-and-dirty scripts. My solution is very nearly what you're asking for. I paste a 10 line snippet of code at the top of any of my scripts that parses the command line and sticks positional args and switches into a Hash object (usually assigned to a object that I've named arghash in the examples below).

Here's an example command line you might want to parse... > ./myexampleprog.rb -s -x=15 --longswitch arg1 --longswitch2=val1 arg2

Which would become a Hash like this.

 { 
   '-s' => true, 
   '-x=' => '15', 
   '--longswitch' => true, 
   '--longswitch2=' => 'val1', 
   0 => 'arg1', 
   1 => 'arg2'
 }

In addition to that, two convenience methods are added to the Hash:

  • argc() will return the count of non-switch arguments.
  • switches() will return an array containing the keys for switches that are present

This is mean to allow some quick and dirty stuff like...

  • Validate I've got the right number of positional arguments regardless of the switches passed in ( arghash.argc == 2 )

  • Access positional arguments by their relative position, regardless of switches appearing before or interspersed with positional arguments ( e.g. arghash[1] always gets the second non-switch argument).

  • Support value-assigned switches in the command line such as "--max=15" which can be accessed by arghash['--max='] which yields a value of '15' given the example command line.

  • Test for the presence or absence of a switch in the command line using a very simple notation such as arghash['-s'] which evaluates to true if it's present and nil if its absent.

  • Test for the presence of a switch or alternatives of switches using set operations like

    puts USAGETEXT if !(%w(-h --help) & arghash.switches()).empty?

  • Identify use of invalid switches using set operations such as

    puts "Invalid switch found!" if !(arghash.switches - %w(-valid1 -valid2)).empty?

  • Specify default values for missing arguments using a simple Hash.merge() such as the below example that fills in a value for -max= if one was not set and adds a 4th positional argument if one was not passed.

    with_defaults = {'-max=' => 20, 3 => 'default.txt'}.merge(arghash)

Solution 14 - Ruby

This is very similar to the accepted answer, but using ARGV.delete_if which is what I use in my simple parser. The only real difference is that options with arguments must be together (e.g. -lfile).

def usage
  "usage: #{File.basename($0)}: [-l<logfile>] [-q] file ..."
end

ARGV.delete_if do |cur|
  next false if cur[0] != '-'
  case cur
  when '-q'
    $quiet = true
  when /^-l(.+)$/
    $logfile = $1
  else
    $stderr.puts "Unknown option: #{cur}"
    $stderr.puts usage
    exit 1
  end
end

Solution 15 - Ruby

Apparently @WilliamMorgan and I think alike. I just released last night to Github what I now see is a similar library to Trollop (Named how?) after having done a search for OptionParser on Github, see Switches

There are a few differences, but the philosophy is the same. One obvious difference is that Switches is dependent on OptionParser.

Solution 16 - Ruby

I'm developing my own option parser gem called Acclaim.

I wrote it because I wanted to create git-style command line interfaces and be able to cleanly separate the functionality of each command into separate classes, but it can also be used without the entire command framework as well:

(options = []) << Acclaim::Option.new(:verbose, '-v', '--verbose')
values = Acclaim::Option::Parser.new(ARGV, options).parse!
puts 'Verbose.' if values.verbose?

No stable release as of yet, but I've already implemented some features like:

  • custom option parser
  • flexible parsing of option's arguments that allows for both minimum and optional
  • support for many option styles
  • replace, append or raise on multiple instances of the same option
  • custom option handlers
  • custom type handlers
  • predefined handlers for the common standard library classes

There's a lot of emphasis on commands so it might be a little heavy for simple command line parsing but it works well and I've been using it on all of my projects. If you're interested in the command interface aspect then check out the project's GitHub page for more information and examples.

Solution 17 - Ruby

Suppose a command has at most one action and arbitrary number of options like this:

cmd.rb
cmd.rb action
cmd.rb action -a -b ...
cmd.rb action -ab ...

The parsing without validation may be like this:

ACTION = ARGV.shift
OPTIONS = ARGV.join.tr('-', '')

if ACTION == '***'
  ...
  if OPTIONS.include? '*'
    ...
  end
  ...
end

Solution 18 - Ruby

https://github.com/soveran/clap

other_args = Clap.run ARGV,
  "-s" => lambda { |s| switch = s },
  "-o" => lambda { other = true }

46LOC (at 1.0.0), no dependency on external option parser. Gets the job done. Probably not as full featured as others, but it's 46LOC.

If you check the code you can pretty easily duplicate the underlying technique -- assign lambdas and use the arity to ensure the proper number of args follow the flag if you really don't want an external library.

Simple. Cheap.


EDIT: the underlying concept boiled down as I suppose you might copy/paste it into a script to make a reasonable command line parser. It's definitely not something I would commit to memory, but using the lambda arity as a cheap parser is a novel idea:

flag = false
option = nil
opts = {
  "--flag" => ->() { flag = true },
  "--option" => ->(v) { option = v }
}

argv = ARGV
args = []

while argv.any?
  item = argv.shift
  flag = opts[item]

  if flag
    raise ArgumentError if argv.size < arity
    flag.call(*argv.shift(arity))
  else
    args << item
  end
end

# ...do stuff...

Solution 19 - Ruby

I created a very simple yet useful parser: parseopt. It uses Git's internal option parser as inspiration, and also Ruby's OptionParser.

It looks like this:

opts = ParseOpt.new
opts.usage = 'git foo'

opts.on('b', 'bool', 'Boolean') do |v|
 $bool = v
end

opts.on('s', 'string', 'String') do |v|
 $str = v
end

opts.on('n', 'number', 'Number') do |v|
 $num = v.to_i
end

opts.parse

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioncjsView Question on Stackoverflow
Solution 1 - RubyWilliam MorganView Answer on Stackoverflow
Solution 2 - RubyrampionView Answer on Stackoverflow
Solution 3 - RubycjsView Answer on Stackoverflow
Solution 4 - RubybjjbView Answer on Stackoverflow
Solution 5 - RubyFlorian PilzView Answer on Stackoverflow
Solution 6 - RubyPeter CooperView Answer on Stackoverflow
Solution 7 - RubychrismealyView Answer on Stackoverflow
Solution 8 - RubyStefanView Answer on Stackoverflow
Solution 9 - RubyJack ChuView Answer on Stackoverflow
Solution 10 - RubyEdwardTeachView Answer on Stackoverflow
Solution 11 - Rubyuser67416View Answer on Stackoverflow
Solution 12 - RubyRogerView Answer on Stackoverflow
Solution 13 - RubyDavid FosterView Answer on Stackoverflow
Solution 14 - RubyFelipeCView Answer on Stackoverflow
Solution 15 - RubythoranView Answer on Stackoverflow
Solution 16 - RubyMatheus MoreiraView Answer on Stackoverflow
Solution 17 - RubyBohrView Answer on Stackoverflow
Solution 18 - RubyBen AlaviView Answer on Stackoverflow
Solution 19 - RubyFelipeCView Answer on Stackoverflow