その1 を書いた後、もうちょっと良さそうな方法を思いついたのでその2。
こっちの方がオプショナル引数の書き方が自然(シェルでの一般的な書き方からの逸脱が少ない)で、他の人に使ってもらうとしても拒否反応が少ないだろうし説明も楽かなと。
自然ということでいえば、その1では必ず "key=value" のフォーマットで書く仕様にしていましたが、やはりフラグとしての指定もできた方がいいかと思ってそこも変更しました。
目的
- 使い捨てのスクリプト、プロトタイピング、自分だけ〜何人かだけで使う程度の軽いツールなどを作るときにサッと使いたい
- なるべく言語非依存にしたい
高機能でなくていいので yak shaving なしでサッと使いたい。
使い方
例として arg1, arg2 が必須で、あとはオプショナルなパラメータとして opt1, opt2, opt3 が指定できるが opt2 は渡さないという場合、次のように指定します。
必須引数 arg1, arg2 の順番は固定。オプショナル引数はその後に置く。
ruby my_cmd.rb arg1 arg2 opt1=11 opt3=33
オプショナル引数はまるごと省略可:
ruby my_cmd.rb arg1 arg2
key=value の形になっていない("=" を含まない)オプショナル引数はフラグとして扱われる。
ruby my_cmd.rb arg1 arg2 enable-foo
実装例
Ruby の場合(説明用コードなので一部 Ruby っぽくない書き方になっていますが意図的なものです)。
def parse_optionals(args) optionals = {} args.each{ |arg| if arg.include? "=" idx = arg.index("=") # assert idx >= 1 k = arg[0 ... idx] v = arg[(idx + 1) .. -1] optionals[k] = v else optionals[arg] = true end } return optionals end def parse_args(args, names) opts = {} if ARGV.size == names.size # オプショナル引数なし elsif ARGV.size > names.size # オプショナル引数あり opts = parse_optionals(ARGV[names.size .. -1]) else $stderr.puts "invalid arguments size" # print_usage exit 1 end names.each_with_index{ |name, i| opts[name] = args[i] } return opts end # コマンドライン引数をパース opts = parse_args( ARGV, ["arg1", "arg2"] ) # 必要に応じてバリデーションとか # パースされた引数を使ったプログラム本体 do_someting( opts )
パース結果を pretty print しただけの動作例:
$ ruby my_cmd.rb a1 a2 opt1=o1 opt2=o2 opt3=o3 {"opt1"=>"o1", "opt2"=>"o2", "opt3"=>"o3", "arg1"=>"a1", "arg2"=>"a2"} $ ruby my_cmd.rb a1 a2 opt1=o1 opt3=o3 {"opt1"=>"o1", "opt3"=>"o3", "arg1"=>"a1", "arg2"=>"a2"} $ ruby my_cmd.rb a1 a2 {"arg1"=>"a1", "arg2"=>"a2"} $ ruby my_cmd.rb a1 a2 enable-foo {"enable-foo"=>true, "arg1"=>"a1", "arg2"=>"a2"}
パース結果は Java でいえば Map<String, String> で、そこから先のバリデーションとかは使う側が適宜行います。