本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーはふしはらかんさんで、テーマは「Perlで作るコマンドラインツール」です。
本稿のサンプルコードは、本誌サポートサイトから入手できます。
コマンドラインツールを作ろう
本連載を読まれているみなさんは、コマンドラインツールを使ったことのある人がほとんどかと思います。また、コマンドラインツールを作ったことのある人も多いでしょう。Perlはほとんどの環境に標準で導入されていて、強力なテキスト処理を備えていることもあり、シェルスクリプトで書くのはしんどいかなという程度のものから、かなり複雑で高負荷な処理を要するものまで、幅広くコマンドラインツールの記述に使われています。
今回は、コマンドラインツールをPerlで作る場合に使用すると便利なモジュールを、コマンドライン引数解析ライブラリであるSmart::Options
を中心に紹介していきます。
Smart::Optionsによるコマンドライン引数の利用
コマンドラインツールを実行する際に、プログラム名に続いて渡す文字列をコマンドライン引数と言います。本節では、コマンドライン引数とそれを使ったオプションの渡し方の仕様から、Smart::Options
の具体的な使い方までを見ていきましょう。
コマンドライン引数はなぜ必要か
コマンドライン引数は空白で区切って複数の値を渡すのが基本ですが、さまざまな設定値をオプションとして渡せるようになっている場合が多く、その仕様がPOSIX(Portable Operating System Interface for UNIX)やGNUでガイドラインとして定められています。
-
(ハイフン)から始まる英字1字(例:-a
)がオプション名です。-bc
のように英字を複数書くと複数のオプションを一度に指定できます。
オプションに値を持たせる場合、-d オプション値
のように空白を空けてから値を書きます。もし値に空白を含めたい場合は、値全体を-d "値 値"
のようにダブルクオートで囲みます。
オプション名を2文字以上にしたい場合は--
(ハイフン2文字)から始めるようにします(例:--execute
)。この場合、値を渡す場合に空白を空けるだけなく、=
(イコール)で結ぶ書き方を使うこともできます(例:--file=path
)。GNUスタイルでは、--version
でのバージョン表示と、--help
でのUsage(コマンドの使い方)の表示が必須です。
多くのプログラミング言語では、コマンドライン引数を空白で区切った文字列の配列として受け取れるようになっています。Perlもコマンドライン引数を特殊変数@ARGV
として参照できるようになっています。
このコードを実行すると、渡されたコマンドライン引数を改行で区切って出力します。
引数をシンプルに利用する
前項のサンプルコードでコマンドライン引数を配列として取得できましたが、それをPOSIXスタイルに沿ったオプションとして扱うのは手間です。たとえば-bc
というオプションからb
オプションとc
オプションが有効になっていることを知ったり、d
オプションの値としてFooBar
が渡されていることを知ったりするには、@ARGV
を解析するプログラムを実装する必要があります。
PerlにはGetOpt::Long
というモジュールが標準添付されているほか、CPANにさまざまなコマンドライン引数解析ライブラリが登録されていて、それらを使ってコマンドライン引数の解析ができます。今回はその中から、拙作のSmart::Options
というライブラリを紹介します。
Smart::Options
はNode.jsのコマンドライン引数解析ライブラリであるoptimist
を参考に実装したもので、前述のPOSIX、GNUスタイルなオプション値をシンプルな書き方で解析して使えるようにしたものです。試しに先ほどのコードをSmart::Options
を使った形にあらためてみます。
argv()
はSmart::Options
によってインポートされた関数で、引数に渡された配列を解析してハッシュリファレンスの形で返します。Data::Dumper
でハッシュの中身をそのまま出力しているので、このコードを実行すると次のようになります。
結果を見てわかるとおり、オプションの項目名(-a
、-b
など)がハッシュのキーになり、オプションの値がハッシュの値として登録されています。また、オプションに関わらないただの引数はキー名が_
の項目に配列リファレンスの形で登録されています。-a
などの値を持たないオプション(フラグ)は、Perlで真(true
)になる1
が値として入っていますので、
のように書けます。ちなみにこのコードではargv()
に引数を渡していませんが、その場合は@ARGV
を引数として使用します。
このようにSmart::Options
を導入してargv()
を使うだけで、コマンドライン引数からオプション値を取り出すのが簡単になります。
オプションを定義して便利に使う
Smart::Options
ではオプションの定義を事前に登録することで、より便利に使えます。
先ほどまではargv()
関数を使ってきましたが、これは実はSmart::Options->new->parse
のショートカットです。Smart::Options
の本来の使い方は、上記のようにSmart::Options
のオブジェクトを生成してオプション定義を登録していき、最後にparse
メソッドを実行して戻り値を受け取る、という方法になります。サンプルコード内のalias
メソッドを含め、これから紹介するオプション定義のメソッドは戻り値として(定義が追加された)オブジェクト自身を返すので、メソッドチェインで複数の定義を1行で書いてそのままparse
を実行できます。
alias ── オプションに別名を付ける
alias
はオプションの別名を定義します。file
というオプション名を短くf
でも指定できるようにする、などに使えます。
以降のコード例では同様の表記が続きますので、変数$opt
はあらかじめSmart::Options->new
で初期化されているものとします。実際に動かしてみる場合は、冒頭にmy $opt = Smart::Options->new;
の一文を追記してください。
demand ── オプションを必須にする
demand
はオプションの指定を必須にします。コマンドライン引数で当該オプションが指定されていない場合、エラーになります。
boolean ── フラグオプションを強制する
boolean
は、指定したオプションがフラグ(値をとらず、オン/オフの判定だけに使われる)として使われることを強制します。次のコードのように引数の解析結果に影響します。
default ── オプションのデフォルト値を指定する
オプションに初期値を持たせるにはdefault
を使います。コマンドライン引数でオプションが指定されていなくても、戻り値に値が含まれるようになります。
help ── オプションの使い方を表示する
help
メソッドを実行すると、定義したオプションの詳細を列挙した使い方(Usage)の文字列を返します。
describe
を使うとオプションの説明を登録し、Usageで出力させることができます。先ほどのコードでは定義していない--help
というオプションが表示されていますが、これはSmart::Options
であらかじめ定義されているオプションで、Smart::Options->new->parse(=argv())
を実行したときに-h
あるいは--help
というオプションがあった場合はUsageを出力してプログラムを終了します。
なお、GNUスタイルで--help
オプションとともに必須となっている--version
オプションはあらかじめ定義はされません。コマンドラインツールのバージョンを指定するルールがPerlに存在しないためです。
type ── オプション値の型をチェックする
type
を使うと、オプションの値の種類に制限をかけられます。
type
としては、Str
(文字列)、Int
(整数)、Num
(数値全般)などを指定できるようになっています。たとえばオプションの値として数値を期待している場合、次のようなコードになります。
さらに、coerce
を使うと独自のtype
を定義できます。たとえばオプションで年/月/日
という文字列を受け取って、プログラム中ではTime::Piece
のインスタンスとして扱いたい場合、Time
というtype
を定義します。
options ── オプションを一括で定義する
ここまで、いろいろなメソッドでオプションの定義を追加する方法を説明してきましたが、options
メソッドを使うと、1つのオプションに複数の定義を行うこともできます。
特殊なコマンドライン引数
Smart::Options
では、いくつかの特殊なオプションの書き方に対応しています。
解析の打ち切り
Smart::Options
では、--
というオプション以降はオプションの解析を打ち切って、ただの引数として解釈します。
--
以降の-c
、-d 100
というコマンドライン引数はそのまま解釈されて、_に配列として渡されます。cartonexec
のように、ほかのコマンドとその引数をコマンドライン引数として受け取って内部で実行するようなプログラムで役に立ちます。
オプションの否定
--no-foo
のようにオプション名の先頭にno-
を付けると、それに続くオプションが偽(false
)になります。通常は単にそのオプションを指定しなければよいだけなのですが、たとえばfoo
オプションがデフォルトでtrue
になるように設定されている場合に、明示的にfalse
するために使います。
オプション値のネスト
--foo.bar
のようにオプション名の中に.
(ドット/ピリオド)を含めると、オプション値を階層構造にできます。
オプションの種類が多くてオプション名が衝突しそうなときなどに使えます。もっとも、そこまで複雑になってきたら後述する設定ファイルを検討したほうがよいケースも多いでしょう。
Smart::Options::Declareによる直感的なコマンドライン引数の利用
argv()
を実行して解析結果のハッシュリファレンスを受け取るSmart::Options
の基本的な使い方は直感的ですが、さらに直感的な書き方を使うことができます。それには、Smart::Options
に付属するSmart::Options::Declare
というモジュールを使います。
opts
に続いてmy
で変数定義をすると、変数名と同じオプションの値が変数に入ります。type
やデフォルト値なども定義できます。
上記のコードでは、@ARGV
の中身が--foo
、string
の配列のとき、$foo
の内容が"string"
という文字列、$bar
の内容が数値の4になります。
サブコマンドを利用する
Gitコマンドは、add
、commit
、pull
、push
などいろいろなサブコマンドを指定して複数の機能を切り替えて実行できます。
Smart::Options
では、このようなサブコマンドを使うコマンドラインツールを実装するためのしくみも持っています。
subcmd
メソッドに、サブコマンド名と、サブコマンドのオプションを定義するSmart::Options
のオブジェクトを渡してサブコマンドを定義していきます。parse
メソッドの戻り値のキー名command
に指定されたサブコマンドが、キー名cmd_option
にコマンドラインオプションの解析結果が入ります。
<続きの(2)はこちら。>
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現!
- 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう
- 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、NFT