【Ruby on Rails】オプション付きのコマンドライン引数を扱う

概要

Ruby 製のスクリプトをサーバの /usr/local/bin などに配置して、自作コマンドとして使おうと思った事があります。

その時、コマンドラインから引数を渡して、引数によって処理を変更したいと考えていました。

例えば scp コマンドのように引数で渡したサーバに対してファイルを転送するみたいな事をやろうとしていました。

Ruby でコマンドライン引数を扱う場合、ARGV を使います。

ただし、出来る事ならオプション付きでコマンドライン引数を扱いたい考えていました。

$ command <servername> で実行するのではなく、-s や --servername オプションを用意して $ command -s <servername> 見たいに実行するイメージです。

更にみんなで使うため、--help オプションも用意して Usage (使用法) が表示できたら便利だなと思っていました。

これらを作るのは結構面倒くさそうだなと思い調べていたら、オプション付きのコマンドライン引数を簡単に実装出来る optparse と言うライブラリを見つけました!

しかも、--help オプションや --version オプションがデフォルトで用意されています。

今回、optparse を利用して Ruby 製の自作コマンドを作った時のことをまとめます。

 

自作コマンド

ソースコードを見せた方が早いと思ったので hello と表示するだけの自作コマンドを用意しました。

ショートオプションとロングオプション両方を用意しています。

require 'optparse'

option = {}
OptionParser.new do |opt|
  Version = '1.0.0'
  # ショートオプション、ロングオプション、オプションの説明
  opt.on('-u', '--uppercase', 'Print hello in uppercase.') { |v| option[:u] = v }
# -m, --message ではオプションの後に値 <MESSAGE> が必須 opt.on('-m', '--message <MESSAGE>', 'Append message after hello.') { |v| option[:m] = v } opt.on('-v', '--version', 'Print version and exit.') { puts "Version #{opt.version}"; exit } opt.on('-h', '--help', 'Print usage and this help message and exit.') { puts opt; exit } begin
# オプション解析してくれる opt.parse! # 存在しないオプションを指定された場合 rescue OptionParser::InvalidOption => e puts "hello: #{e.message}" puts opt exit # オプション値が空だった場合 (--message の場合のみ) rescue OptionParser::MissingArgument => e puts "hello: #{e.message}" puts opt exit end end output = 'hello' output << " #{option[:m]}" if option[:m] output.upcase! if option[:u] puts output

--help (-h) オプション

--help を付けると以下のように表示されます。

--version と --help はデフォルトで用意されているので opt.on( ) に記載する必要はありません。

しかし、opt.on( ) に記載してないオプションは、 --help を付けた時に Usage (使用法) に表示されないので、わざわざ記載しています。

$ ./hello.rb -h
Usage: hello [options]
    -u, --uppercase                  Print hello in uppercase.
    -m, --message <MESSAGE>          Append message after hello.
    -v, --version                    Print version and exit.
    -h, --help                       Print usage and this help message and exit.

--version (-v) オプション

何も指定していないと version unknown になってしまいます。

折角なので Version = '1.0.0' としています。

これを設定すると opt.version で、コマンドの Version が取得出来るようになります。

# 何も指定していない場合
$ ./hello.rb -v
hello: version unknown

$ ./hello.rb -v
Version 1.0.0

--message (-m) オプション

--message の後ろに <MESSAGE> と書いていますが、これは --message を使う時にはオプション値が必要という意味になります。

--meessage の後ろに値が設定されていない場合は mission argument エラーとなります。

# -m オプションの後に値がない場合は missing argument エラーとなる
$ ./hello.rb -m
hello: missing argument: -m
Usage: hello [options]
    -u, --uppercase                  Print hello in uppercase.
    -m, --message <MESSAGE>          Append message after hello.
    -v, --version                    Print version and exit.
    -h, --help                       Print usage and this help message and exit.

# -m オプションの後に値を付けて実行する
$ ./hello.rb -m 'world!'
hello world!

--uppercase (-u) オプション

--uppercase を付けると大文字表示になります。

--message と併用もできます。(これはコマンドの作り方にもよります)

ただし、ショートオプションで -m と -u を合わせて -mu みたいに繋げて使うことは出来ないようです。

$ ./hello.rb -m 'world!' -u
HELLO WORLD!

# ショートオプションを繋げて使おうとすると想定外の結果となる $ ./hello.rb -mu 'world!' hello u

 

例外処理

基本的に存在しないオプションを指定された場合と、--message のようにオプション値が空だった場合の2パターンを抑えておけは充分だと思います。

例外発生時にはエラーメッセージと puts opt で Usage (使用法) を表示するようにしています。


    # 存在しないオプションを指定された場合
$ ./hello.rb -a
hello: invalid option: -a
Usage: hello [options]
    -u, --uppercase                  Print hello in uppercase.
    -m, --message <MESSAGE>          Append message after hello.
    -v, --version                    Print version and exit.
    -h, --help                       Print usage and this help message and exit.

# オプション値が空だった場合    
$ ./hello.rb -m
hello: missing argument: -m
Usage: hello [options]
    -u, --uppercase                  Print hello in uppercase.
    -m, --message <MESSAGE>          Append message after hello.
    -v, --version                    Print version and exit.
    -h, --help                       Print usage and this help message and exit.

 

詰まったポイント

オプション値が必要な場合は明示的に指定する

# オプション値 <MESSAGE> あり
opt.on('-m', '--message <MESSAGE>', 'Append message after hello.') { |v| option[:m] = v }

# オプション値 <MESSAGE> なし
opt.on('-m', '--message', 'Append message after hello.') { |v| option[:m] = v }

と書いた場合、$ ./hello.rb -m 'world!' を実行して option[:m] に格納される値は world! ではなく true となります。

--message <MESSAGE> のように、明示的にオプション値が必須だと宣言しない限り、そのオプションが存在するかどうかの true/false が返り値となるようです。

optparse のバグ?

多分、前方一致になっているようで、ロングオプションを全部記述しなくても動いてしまいます。

# --message を --mes と短縮
$ ./hello.rb --mes 'world!'
hello world!

# --version を --ver と短縮
$ ./hello.rb --ver
Version 1.0.0

 

最後に

コマンド化する

今までローカルの hello.rb スクリプトを実行してたので /usr/local/bin に移動します。

# シンボリックリンクを貼る (絶対パスで指定する)
$ sudo ln -s /Users/<個人アカウント>/hello.rb /usr/local/bin/hello # Mac の場合
# もし間違えた場合
# $ unlink /usr/local/bin/hello

# 実行権限を付与する
$ chmod +x /usr/local/bin/hello

# 確認
$ which hello
/usr/local/bin/hello

# 実行
$ hello -m 'world!'
hello world!

 

まとめ

optparse ライブラリを使って Ruby 製の自作コマンドを作ってみました。

Ruby では ARGV を使えばコマンドライン引数を扱えるのですが、引数が 2 個や 3 個と増えた場合に、だんだん面倒になると思います。

何故なら 2 番目の引数がなかった場合のことや、2 番目と 3 番目が逆に設定された場合のことなどを考えないといけないからです。

オプション付きのコマンドライン引数であればそれらを考える手間が省けます。

--help オプションなど便利な機能がデフォルトで用意されていますし、Ruby 製の自作コマンドを作成する際には是非 optparse を使って見てください。