Really Cheap Command-Line Option Parsing in Ruby

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

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.

How can I do really cheap command line parsing in Python?

You might want to use sys.argv:

from sys import argv
quiet = '-d' in argv # True if '-d' in argv else False

If you want to remove '-d' from the argv, change the second line to this:

quiet = '-d' in argv and (argv.remove('-d') or True)

If this reduces its cheapness, let's make a function of it:

getArg = lambda x: x in argv and (argv.remove(x) or True)
quiet = getArg('-d')
interactive = getArg('-i')

Parse multiple command line options in Ruby using OptionParser

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.

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

Ruby Option Parser. Is there more concision possible for this code?

The trollop gem might be of interest to you. It has long/short options, default values, required flags, etc. Use of it is not unlike what you have right now.

Encoding problems with ruby while reading in command line arguments with optparse

Adding zip_file_name.force_encoding(Encoding::Windows_1252) before opening the file solves the issue (on Western Europe Windows).

Apparently, the CP850 file names encoding is a wrong assumption from Ruby. On my Windows system, it seems that filenames are encoded in Windows_1252 (a custom version of Latin1 or ISO 8859-1).



Related Topics



Leave a reply



Submit