やなぎにっき

学んだことの記録

コマンドライン引数を扱う際の注意点(ARGV.getopts)

フィヨルドブートキャンプではRubyのプラクティスの一つに「Rubyでlsコマンドをつくる」という課題がある。

その課題作成中にコマンドライン引数を扱う際につまずいたことのメモ。

やりたいこと

ls.rbコマンドのオプションとディレクトリ名を引数として受け取りたい。

コマンド:$ ls.rb -alr <ディレクトリ名>

参照したこと

ARGVとは

ARGVをプログラム中に呼び出すことでコマンドライン引数を配列で受け取ることができる。

Object::ARGV (Ruby 2.7.0 リファレンスマニュアル)

サンプル

  • プログラム
p ARGV
  • 実行シェルコマンド
$ ruby sample.rb foo bar baz
["foo", "bar", "baz"]

ARGVでオプションを取得するには

optparse を require することにより、OptionParser::Arguable の機能を使うことができる。
ARGV.getops(short_opt, *long_opt)でオプションを保持したHashを得ることができる。 OptionParser::Arguable#getopts (Ruby 2.7.0 リファレンスマニュアル)

サンプル

  • プログラム
#!/usr/bin/env ruby
require 'optparse'
params = ARGV.getopts('a:b:', 'foo', 'bar:')
p params
  • 実行結果
$ ruby sample.rb -a 1 --foo --bar xxx
{"a"=>"1", "b"=>nil, "foo"=>true, "bar"=>"xxx"}

試したことと分かったこと

まずp ARGVでどのような値が取得できるか見てみる。

#!/usr/bin/env ruby
require 'optparse'

p ARGV
$ ls.rb -a    
["-a"]
$ ls.rb -arl   
["-arl"]
$ ls.rb -a test
["-a", "test"]

配列で取得できた。

次はオプションを取得するためにgetoptsを使ってみる。

~省略~

p ARGV.getopts('alr')
$ ls.rb -a
{"a"=>true, "l"=>false, "r"=>false}
$ ls.rb -arl
{"a"=>true, "l"=>true, "r"=>true}
$ ls.rb -a test
{"a"=>true, "l"=>false, "r"=>false}

オプションは取得できたが、オプションの後のディレクトリ名は取得できない。

getoptsの後にARGVを取得してみる。
$ ls.rb -a test を実行すれば {"a"=>true, "l"=>false, "r"=>false, "test"}のように取得できるのではないか。

~省略~

p ARGV.getopts('alr')
p ARGV
$ ls.rb -a test
{"a"=>true, "l"=>false, "r"=>false}
["test"]

引数は取得できたが、予想に反してtestだけだった。 試しにgetoptsの前でもARGVを参照してみる。

~省略~

p ARGV
p ARGV.getopts('alr')
p ARGV
$ ls.rb -a test
["-a", "test"]
{"a"=>true, "l"=>false, "r"=>false}
["test"]

どうやら、AGRV.getoptsを実行すると、ARGVから取得した値を削除する模様。
るりまには「破壊的な変更」のような記載はないけど、自身をパースがこのような挙動の説明になるっぽい……?

今回は以下のようにAGRV.getoptsでオプションの引数を取得した後にAGRVディレクトリ名として取得することで解決した。

options = ARGV.getopts('alr')
input_dir = ARGV