Python風getopt

30分プログラム、その165。Python風getopt。
昔何かでPythonのgetoptは実に使いやすい、という記述を読んだ気がするので、Rubyに適当に移植してみる。

どう使いやすいかというと、RubyのOptionParserは次のように書く。

require 'optparse'

option = []
opt = OptionParser.new
opt.on('-a') {|v| option['a'] = true }
opt.on('-b') {|v| option['b'] = true }

p opt.parse!(ARGV)
p option

# ruby sample.rb -a foo bar -b baz
# => ["foo","bar","baz"]
#    {"a"=>true, "b"=>true}

これが、Pythonのgetoptだと

import getopt
optlist, args = getopt.getopt(sys.argv[1:], 'ab')
print args
print optlist

# pthyon sample.py -a foo bar -b baz
# => ["foo","bar","baz"]
#    [('-a',''),('-b','')]

Pythonのgetoptだと、OptionParserを作る必要もない上に、オプションを覚えておく変数を自分で作らなくてもいい。
その代りRubyのOptionParserよりも機能が劣るけれど、さっと書くだけのプログラムならgetopt程度で十分。

なので、getoptをRubyに移植。自前でコマンドライン解析を書くと死にそうだったので、OptionParserに丸投げしてる。

使い方

連想リストはあまり好きじゃないので、ハッシュに変更した。

irb> require 'getopt'

# 短いオプションの場合
irb> args = '-a -b -cfoo -d bar a1 a2'.split
=> ["-a", "-b", "-cfoo", "-d", "bar", "a1", "a2"]
irb> optlist,args = getopt(args,'abc:d:')
irb> optlist
=> {"a"=>true, "b"=>true, "c"=>"foo", "d"=>"bar"}
irb> args
=> ["a1", "a2"]

# 長いオプションの場合
irb> args = '--condition=foo --testing --output-file abc.def -x a1 a2'.split
=> ["--condition=foo", "--testing", "--output-file", "abc.def", "-x", "a1", "a2"]
irb> optlist,args = getopt(args,'x',['condition=','output-file=','testing'])
irb> optlist
=> {"x"=>true, "condition"=>"foo", "output-file"=>"abc.def", "testing"=>true}
irb> args
=> ["a1", "a2"]

ソースコード

#! /opt/local/bin/ruby -w
# -*- mode:ruby; coding:utf-8 -*-
#
# getopt.rb - Python's getopt for Ruby
#
# Copyright(C) 2007 by mzp
# Author: MIZUNO Hiroki <hiroki1124@gmail.com>
# http://mzp.sakura.ne.jp/
#
# Timestamp: 2007/10/26 21:08:00
#
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Ruby itself.
#
require 'optparse'
def getopt(args,options,long_options=[])
  parser = OptionParser.new

  option_result = {}

  # 短いオプション(-a,-b VAL)の追加
  # a,b:のように書く
  options.scan(/.:?/).each{|option|
    # オプション名
    name      = option[0].chr

    # パーサに渡す文字列
    parse_str = '-' + option.sub(/:\Z/,' VAL')

    parser.on(parse_str){|value|
      option_result[name] = value
    }
  }

  # 長いオプション名(--foo,--bar=VAL)の追加
  # foo,bar=のように書く
  long_options.each{|option|
    # オプション名とパーサ用文字列
    name      = option.sub(/=\Z/,'')
    parse_str = '--' + option.sub(/=\Z/,'=VAL')

    parser.on(parse_str){|value|
      option_result[name] = value
    }
  }

  parsed = parser.parse args
  [option_result,parsed]
end


if __FILE__ == $0 then
  args = '-a -b -cfoo -d bar a1 a2'.split
  optlist,args = getopt(args,'abc:d:')
  p optlist
  p args
#  optlist,args = getopt('-a -b -cfoo -d bar --test --output-file=a a1 a2'.split,
#n                        'abc:d:',
#                        ['test','output-file='])
#  p optlist
#  p args
end