初めに
今回は、Ruby
のoptparse
ライブラリについて自分なりに噛み砕いて説明していきます。
噛み砕いたところで公式となんら変わらないところは飛ばしたり、とてもさらっといきます。
まず、どういうものなのかのイメージを持ってもらうため、下記を見てください。
(※全て記載すると不要に場所を取るので削ってます)
ZumiAir ruby % ruby -h
Usage: ruby [switches] [--] [programfile] [arguments]
-v print the version number, then turn on verbose mode
-w turn warnings on for your script
-W[level=2|:category] set warning level; 0=silence, 1=medium, 2=verbose
-x[directory] strip off text before #!ruby line and perhaps cd to directory
--jit enable JIT for the platform, same as --mjit (experimental)
--mjit enable C compiler-based JIT compiler (experimental)
-h show this message, --help for more info
何かしら、ターミナルでコマンドを打つ場合にオプション指定できるものがほとんどだと思います。
例であげたものだと、-h
を指定していますね。
これを自分が作成したプログラムにも楽に定義するためのライブラリになります。
いくつかの定義方法を解説していきますが、どれが一番直感的に使えるのかというと
色々書いたけど簡単なのは
が個人的にはいいかなと思います。
とりあえずオプションを受け付けるようにしたい!という方はこの項を見ていただくとよいかなと。
オプションを受け付けるようにしてみよう
オプションの登録ですが、on
メソッドを使用します。
下記は、-a
オプションが指定されて実行されるとtrue、なければfalseが表示される想定です。
require 'optparse'
opt = OptionParser.new
opt.on('-a') {|v| p v}
opt.on('-b') {|v| p v}
ZumiAir ruby % ruby optparse_sample.rb -a 1 2 -b 3
...何も表示されません。
公式ドキュメント(optparse)を見ると、
Enumerator#each などと違い、OptionParser#on メソッドが呼ばれた時点ではブロックは実行されません。あくまで登録されるだけです。OptionParser#parse が呼ばれた時に、コマンドラインにオプションが指定されていれば実行されます。
とあります。
つまり、上記の例では-a
や-b
オプションの登録はされているけれど、{|v| p v}
は実行されないということ。
では、parse
メソッドを追記してみます。
require 'optparse'
opt = OptionParser.new
opt.on('-a') {|v| p v}
opt.on('-b') {|v| p v}
argv = opt.parse(ARGV)
p ARGV
p argv
ZumiAir ruby % ruby optparse_sample.rb -a 1 2 -b 3
true
true
["-a", "1", "2", "-b", "3"]
["1", "2", "3"]
期待通り表示されました!
ちなみに、定義していないオプションを指定すると例外が発生します。
普段使うコマンドでも無いオプションを指定すると怒られると思いますが、同じですね。
ZumiAir ruby % ruby optparse_sample.rb -c
optparse_sample.rb:6:in `<main>': invalid option: -c (OptionParser::InvalidOption)
optparse_sample.rb:6:in `<main>': invalid option: c (OptionParser::InvalidOption)
そして、実はこの時点で-h
オプションは使えます。
共通認識である、そのコマンドのヘルプが表示されます。
ZumiAir ruby % ruby optparse_sample.rb -h
Usage: optparse_sample [options]
-a
-b
説明はありませんが、定義したオプションは表示されていますね。
後でこちらについても記載しています。
オプションの順番
公式ドキュメントに下記の記載がある通り、直後である必要はありません。
オプションの指定はコマンドの直後である必要はありません(上の例で、-b はオプションとして認識されている)。
これは、順番を入れ替えても認識してくれるということになります。
ZumiAir ruby % ruby optparse_sample.rb -b 1 2 -a 3
true
true
["-b", "1", "2", "-a", "3"]
["1", "2", "3"]
しかし、次の文に
ただし、環境変数 POSIXLY_CORRECT が定義してあるとこの挙動は変更されます。
とあります。
正直、何を言ってるかわかりませんでした。笑
自分がいくらか手元で試してわかったのは、
- 公式ドキュメントは
POSIXLY_CORRECT=1
で定義していたが、定義されているかどうかだけなので1
の数字には特に意味はない。 - 実行時の1つ目の引数がオプションでなければ一つもオプションとして認識されない
ZumiAir ruby % env POSIXLY_CORRECT='' ruby optparse_sample.rb a -b -a a -b test baz ["a", "-b", "-a", "a", "-b", "test", "baz"] ⇦ parse後 ["a", "-b", "-a", "a", "-b", "test", "baz"] ⇦ parse前
- 実行時の1つ目の引数がオプションの場合、1つ目だけをオプションと認識する
ZumiAir ruby % env POSIXLY_CORRECT='' ruby optparse_sample.rb -b a -b test baz ["a", "-b", "test", "baz"] ⇦ parse後 ["-b", "a", "-b", "test", "baz"] ⇦ parse前
- 実行時の1つ目の引数がオプションの場合かつ、オプションが続く限りはオプションと認識し、オプション以外が入った時点以降からはオプションとして認識されない
ZumiAir ruby % env POSIXLY_CORRECT='' ruby optparse_sample.rb -b -a a -b test baz ["a", "-b", "test", "baz"] ⇦ parse後 ["-b", "-a", "a", "-b", "test", "baz"] ⇦ parse前
というのが確認できました。
使用頻度はおそらくほぼないので、POSIXLY_CORRECT
を定義すると順番を意識する必要がある。
と認識しておく程度で良いと思います。
どうやって値を取り出そうか
配列で取得する
先ほどの例に、しれっとARGVが登場しましたが、これは与えられた引数を表す配列です。
そのため、出力を見るとARGV
には引数に記載した全てが配列で格納されているのが見えますね。
また、次の行にはparse
メソッドで解析後の配列が格納されています。
オプションが取り除かれてますね。
配列になっているので、下記のように取り出すことはできます。
require 'optparse'
opt = OptionParser.new
opt.on('-a') {|v| p v}
opt.on('-b') {|v| p v}
parse = opt.parse(ARGV)
p parse
arg = parse[1]
p arg
が、これでは使いにくいですよね。
簡単に期待通りの値が取れなくなりそうです。
それならば、明示的に別の入れ物に格納してあげましょう。
require 'optparse'
opt = OptionParser.new
params = {}
opt.on('-a') {|v| params[:a] = v}
opt.on('-b') {|v| params[:b] = v}
parse = opt.parse(ARGV)
p parse
p params
p params[:a]
ZumiAir ruby % ruby optparse_sample.rb -a a_arg -b b_arg
["a_arg", "b_arg"]
{:a=>true, :b=>true}
true
これであれば、一定に各オプションの値を得られそうです。
ただ、現時点では定義したオプション指定の有無しか取れていません。
オプションの引数を取得する(必須)
非常に簡単に取得することができます。
「オプション定義時に末尾に何かしらを記述する」だけです。
require 'optparse'
opt = OptionParser.new
params = {}
opt.on('-a val') {|v| params[:a] = v}
opt.on('-b') {|v| params[:b] = v}
parse = opt.parse(ARGV)
p parse
p params
p params[:a]
ZumiAir ruby % ruby optparse_sample.rb -a a_arg -b b_arg
["b_arg"]
{:a=>"a_arg", :b=>true}
"a_arg"
-a
オプションには引数を取るように記述したことで、期待通り取得できています。
これは、引数が必須になる書き方なので、
ruby optparse_sample.rb -a -b b_arg
["b_arg"]
{:a=>"-b"}
"-b"
のように-b
オプションのつもりで指定したものが-a
オプションの引数として認識されます。
上記例は期待通りでないにしろ、-a
オプションの引数があるとズレて認識されるので例外は発生していません。
ZumiAir ruby % ruby optparse_sample.rb -a
optparse_sample.rb:7:in `<main>': missing argument: -a (OptionParser::MissingArgument)
のように代わりに取れるものもないと、例外が発生します。
オプションの引数を取得する(任意)
オプションの引数を任意(有無を問わない)とする場合、先ほどオプション定義時に末尾に記述したものを[]
で囲みます。
opt.on('-a [val]') {|v| params[:a] = v}
ZumiAir ruby % ruby optparse_sample.rb -a
[]
{:a=>nil}
nil
ZumiAir ruby % ruby optparse_sample.rb -a -b b_arg
["b_arg"]
{:a=>nil, :b=>true}
nil
すると、-a
オプションに引数がなくても例外は発生せずnil
となりました。
また-a
、-b
オプションを続けて記述するケースでも、-a
オプションの引数はnil
と正しく認識されています。
ハッシュで取得する
先ほどは配列に取得した引数ですが、ハッシュで受け取ることもできます。
parse
メソッドの引数にinto
オプションを指定することで自動でハッシュに格納してくれます。
require 'optparse'
opt = OptionParser.new
params = {}
opt.on('-a [val]') {|v| p v}
opt.on('-b', '--long-b') {|v| p v}
parse = opt.parse(ARGV, into: params)
p parse
p params
p params[:b]
ZumiAir ruby % ruby optparse_sample.rb -a -b b_arg
nil
true
["b_arg"]
{:a=>nil, :"long-b"=>true}
true
こちらでは明示的に格納しているわけでなく、自動でハッシュへ格納しています。
ハッシュのキーはロングオプションが定義されていればロングオプションの値を、ショートオプションのみの場合はショートオプションの値が使われます。
オプションの種類
ロングオプション / ショートオプション
馴染みがあるところだと思いますので、さらっといきます。
ZumiAir ruby % ruby --version
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin23]
ZumiAir ruby % ruby -v
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin23]
のように、--
で始まるものがロングオプション、-
で始まるものがショートオプションです。
1つの機能にロング、ショートのオプションがあるというのが多くあるケースでは無いでしょうか。
否定型オプション
ロングオプションにだけ、--
の後ろにno-
を挟んでからオプション文字を入れると否定型オプションを定義することもできます。
require 'optparse'
opt = OptionParser.new
opt.on('--no-along') {|v| p v }
opt.on('-b', '--blong') {|v| p v }
p opt.parse(ARGV)
p ARGV
ruby % ruby no_option_sample.rb --no-along -b
false
true
[]
["--no-along", "-b"]
指定されているとfalse
を返すようになっています。
その後の処理でfalse
で`で返ってきた方が都合がいい場合に使うのかな?とは思うのですが、処理側を工夫すれば回避できるケースが多いので、このようなややこしい指定は使わない方がいいのでは...と個人的には思います。
ヘルプオプション
最初の方にも少し触れましたが、デフォルトで-h
、--help
は定義されています。
試しに、ここまでで記載してきたsampleを-h
オプションをつけて実行してみます。
ZumiAir ruby % ruby optparse_sample.rb -h
Usage: optparse_sample [options]
-a [VAL]
-b, --long-b
こちらも見慣れたものだと思いますが、何か足りない気がしますね。
並びとしては、
ショートオプション、ロングオプション、引数、説明文
と続きます。
はい、現状だとオプションの説明文がありません。
追加するには、on
メソッドの引数に追加してあげます。
require 'optparse'
opt = OptionParser.new
params = {}
opt.on('-a [val]', 'description') {|v| params[:a] = v}
opt.on('-b', '--long-b', '日本語でも書けるけど、英語が無難かな') {|v| params[:b] = v}
parse = opt.parse(ARGV)
ZumiAir ruby % ruby optparse_sample.rb -h
Usage: optparse_sample [options]
-a [val] description
-b, --long-b 日本語でも書けるけど、英語が無難か
このように、自由に説明文を追加できます。
バージョンオプション
人に使ってもらうケースでもなければ特に要らない気はしますが、-v
、--version
オプションもデフォルトで定義されています。
しかし、何もしていないと
ZumiAir ruby % ruby optparse_sample.rb -v
optparse_sample: version unknown
とunknown
になります。
トップレベルにVersion = 任意のVersion
を追加してあげると、
ZumiAir ruby % ruby optparse_sample.rb -v
optparse_sample 2.1.1
のように表示されるようになります。
色々書いたけど簡単なのは
ARGVの機能を使って定義、取得する
optparse
ライブラリをrequire
した際にARGV
が拡張されます。
その中にgetopts
メソッドがあり、これを利用するのが簡単です。
自動で指定されたオプションに応じて解析してくれて、結果をハッシュで返してくれます。
require 'optparse'
parse = ARGV.getopts('ab:', 'long-b:', 'default:1')
p parse
ZumiAir ruby % ruby argv_sample.rb -a -b b_arg --long-b lb-arg
{"a"=>true, "b"=>"b_arg", "long-b"=>"lb-arg", "default"=>"1"}
ここまで解説した中で最も短く書けました!
getopts
メソッドは、第一引数にショートオプション、第二引数以降はロングオプションを記述します。
ショートオプション1個しか作れないの?ということはなく、abc
など連結して書くことで複数定義したことになります。
また、引数を取得するか否かについては:
をオプション直後に付けます。
a:bc:
と書けば、a
とc
オプションは引数が必要ということになります。
注意点としては、[]
で囲い、任意にする記述方法はこちらには無いようです。
--
以降をオプションと認識しないというのは同じになります。
補足
ロングオプション、指定時に全部書かなくてもいい
例えば、--lllong
と--llong
のオプションがあったとします。
その時、どちらのオプションかの区別は3文字目がl
かo
かで判断できますのよね?
なので、この場合は--lll
と指定すれば--lllong
オプションを指定したことになります。
同様に、--llo
と指定すれば--llong
を指定したと認識してくれます。
最後に
ここまで読んでいただき、ありがとうございました!
公式ドキュメントのサブコマンドについても触れようかと思ったのですが、力尽きました。。