Rubyのoptparseについて掘り下げてみる
これは、「フィヨルドブートキャンプ Part 2 Advent Calendar 2021」2日目の記事です。
part1もあるので、こちらもどうぞ!
はじめに
何かしらのプログラミング言語を使ってコマンドを実装する場合、付属するオプションを自前で解析するのは時間がかかりますよね。
Rubyではオプション解析を良い感じにやってくれるライブラリが用意されており、それがoptparse
です。
スクリプトを書くときにoptparse
を何回か使ったことがあるのですが、いまいち理解しきれていないと感じているので、この機会にoptparse
ライブラリについて少し深掘りしたいと思います。
optparseとは
- Rubyには、最初から数多くのライブラリが標準ライブラリとして用意されている
- 特に使用頻度が高いものは組み込みライブラリとして提供されているため、
require
しなくても呼び出すことが可能 optparse
は標準ライブラリの1つで、コマンドラインのオプションを取り扱うために使われるoptparse
を使うときは基本的に以下のステップを踏むOptionParser
オブジェクトoptを生成- オプションを扱うブロックを登録
- opt.parse(ARGV)でコマンドラインをパースする
基本的な使い方
まずは、Rubyのリファレンスに記載されているサンプルコードを使って挙動を確認していきます。
require 'optparse' # create opt object opt = OptionParser.new # register options opt.on('-a') {|v| p v } opt.on('-b') {|v| p v } # check arguments p ARGV # parse opt.parse!(ARGV) p ARGV
実行結果
$ ruby sample.rb -a foo -b bar true true ["-a", "foo", "-b", "bar"] ["foo", "bar"]
on
メソッドは、opt
オフジェクトに登録するオプションを定義しています。ARGV
にはコマンドラインで渡されたオプション、引数が配列として渡されます。パースすることで引数だけ取り出すことができます。
ARGV
からオプションを取り除く必要がない場合は、破壊的メソッドは使わずにopt.parse(ARGV)
すればOKです。
上記のサンプルプログラムでは、オプションa,bを定義しましたが、ヘルプオプションh
はデフォルトで定義されています。
$ ruby sample.rb -h ["-h"] Usage: sample [options] -a -b
ここから、このサンプルプログラムをベースに少しずつ拡張していきます。
ロングオプションを定義する
例えば、ショートオプションだけでななく、ロングオプションを定義したい場合は、on
メソッドでオプション定義するときにショートオプションと一緒にロングオプションとして渡したい値を指定します。
以下のように指定することで、ショート、ロングバージョンのどちらでもオプションを指定できるようになります。
require 'optparse' opt = OptionParser.new opt.on('-a', '--all') {|v| p v } opt.on('-b', '--before') {|v| p v } opt.parse(ARGV) p ARGV
$ ruby sample.rb --all foo --before bar true true ["--all", "foo", "--before", "bar"]
ヘルプを見てみてもショート、ロングバージョンのどちらも表示されていることがわかります。
$ ruby sample.rb -h Usage: sample [options] -a, --all -b, --before
オプションの説明を追記する
ヘルプを表示したときに使えるオプションだけではなく、そのオプションの説明を追加したいと思います。
その場合は、ロングオプション同様にon
メソッドに説明文を渡してあげればOKです。
require 'optparse' opt = OptionParser.new opt.on('-a', '--all', 'description:a') {|v| p v } opt.on('-b', '--before', 'description:b') {|v| p v } opt.parse(ARGV) p ARGV
ヘルプオプションを指定すれば説明文が表示されるようになります。
$ ruby sample.rb -h Usage: sample [options] -a, --all description:a -b, --before description:b
どのオプションが指定されたか記憶する
OptionParser
はどのオプションが指定されたかは記憶していません。そのため、どのオプションがコマンドラインで渡されてきたか覚えておく必要がある場合は、コンテナに格納しておきます。
require 'optparse' opt = OptionParser.new option = {} opt.on('-a', '--all', 'description:a') {|v| option[:a] = v } opt.on('-b', '--before', 'description:b') {|v| option[:b] = v } opt.parse(ARGV) p ARGV p option
オプションが指定された場合は、コンテナにkey,valueが保存されます。オプションが指定されなかった場合は、nilが返ります。
$ ruby sample.rb -a foo -b bar ["-a", "foo", "-b", "bar"] {:a=>true, :b=>true} $ ruby sample.rb -a foo ["-a", "foo"] {:a=>true}
オプションに引数が必須か否か指定する
引数を必須にする場合は、onメソッドでオプション登録するときに末尾に何かしら文字列を追加します。
require 'optparse' opt = OptionParser.new option = {} # 引数なし opt.on('-a', '--all', 'description:a') {|v| option[:a] = v } # 引数必須 opt.on('-b VAL', '--before', 'description:b') {|v| option[:b] = v } # 引数必省略可能 opt.on('-c [VAL]', '--ccc', 'description:c') {|v| option[:c] = v } opt.parse!(ARGV)
引数必須な場合に引数を渡さないと以下のようなエラーが発生します。
$ ruby sample.rb -b sample.rb:8:in `<main>': missing argument: -b (OptionParser::MissingArgument)
また、引数をオプションと一緒に渡すことで引数をそのままHashにkeyとvalueを格納できます。 引数なしのオプションには真偽値が返り、オプションb,cに引数を渡すと引数がHashに入ります。
$ ruby sample.rb -a -b test -c test {:a=>true, :b=>"test", :c=>"test"}
受け取る引数のクラスを指定する
あるオプションの引数で受け取るクラスを限定したい場合は、以下のように記述します。
require 'optparse' opt = OptionParser.new option = {} # 引数はIntegerのみ opt.on('-a', '--all=N', Integer, 'description:a') {|v| option[:a] = v } # 引数はStringのみ opt.on('-b', '--before=S', String, 'description:b') {|v| option[:b] = v } opt.on('-c [VAL]', '--ccc', 'description:c') {|v| option[:c] = v } opt.parse!(ARGV) p option
Interger
クラスを指定すると、文字列が引数として渡ってきたらエラーを返します。また、String
クラスを指定すると、数値を引数に渡した場合でも文字列として受け取ります。
$ ruby sample.rb -a 1 -b aaa {:a=>1, :b=>"aaa"} $ ruby sample.rb -a aaa sample3.rb:7:in `<main>': invalid argument: -a aaa (OptionParser::InvalidArgument) $ ruby sample.rb -b 1 {:b=>"1"}
便利なARGVの機能
今まで紹介した方法でコマンドのオプションや引数を処理することが可能になりますが、もう少し楽にオプションや引数を扱えるのがOptionParser::Arguable
モジュールのgetopts
メソッドです。
getopts
メソッド使うとワンラインでコードでオプションと引数を指定することができ、受け取ったものをHashとして受け取ることができます。
getopts
メソッドにはショートオプション、またはロングオプションを指定することができます。引数が必要なオプションにはコロンをオプションの後ろにつけます。オプションはopt.on
のときと異なり、1つ1つ指定するのではなくまとめて記述するので、ぱっと見わかりずらいかと思いますが、オプションや引数をシンプルに扱うだけであればgetopts
メソッド使うととても楽になります。
require 'optparse' # 引数なしのオプションa, 引数ありのオプションbを指定 params = ARGV.getopts("ab:") p params
$ ruby sample2.rb -h Usage: sample2 [options] -a -b VAL $ ruby sample2.rb -a -b bbb {"a"=>true, "b"=>"bbb"}
ロングオプションを使いたい場合も同様に引数が必要なオプションにはコロンをオプションの後ろにつけますが、ショートオプションのときのようにまとめてオプションは指定せず、分けて書きます。ショートオプションが不要な場合は、""
のように空文字を渡してあげる必要があります。
require 'optparse' # ショートオプションなし、ロングオプション引数なし or あり params = ARGV.getopts("", "all", "before:") p params
$ ruby sample2.rb -h Usage: sample2 [options] --all --before=VAL $ ruby sample2.rb --all --before 1 {"all"=>true, "before"=>"1"}
また、オプションのデフォルト値も指定することが可能です。
require 'optparse' # ショートオプションなし、ロングオプション引数あり、デフォルト値あり params = ARGV.getopts("", "all", "before:", "maxsize:1024") p params
$ ruby sample2.rb -h Usage: sample2 [options] --all --before=VAL --maxsize=1024 $ ruby sample2.rb {"all"=>false, "before"=>nil, "maxsize"=>"1024"}
デフォルト値はコマンド実行時に上書きすることも可能です。
$ ruby sample2.rb --maxsize=100 {"all"=>false, "before"=>nil, "maxsize"=>"100"}
まとめ
実際に調べて使ってみた感想としては、こんな感じです。
- optparse使うと自動的にヘルプオプションついてるの助かる(コマンドのバージョン表示オプションvもついてるらしい)
- 引数クラス指定できるので、後の処理で引数のクラスどうこう考えなくて良いの助かる
- 細かくオプション定義する必要がなければ、
getopts
メソッドを使うとめっちゃ楽になる
なんとなく理解して使っていたライブラリも調べてみると新しい発見もあり理解度があがるので、また気になるライブラリ等あれば深掘りする時間を取ろうと思いました。