2013年2月 2日
2013年のGetopt::Long
完璧な引数処理モジュールなどといったものは存在しない。完璧なGetopt::Longが存在しないようにね。
バッドノウハウの宝庫として有名なGetopt::Longですが、なんだかんだでデファクトで、gnu parallel等、名の知れたコマンドラインツールで使われていたりします。標準モジュール縛りでサクッとコマンドラインツールを書くこともあるでしょうし、そうではなくても、Getopt::Longで片付くことも多いので、個人的なベタープラクティスとかtipsとかを書きます。
Perlでコマンドラインオプションの解析に Getopt::Long を使う時、絶対に忘れてはいけない引数
大事なことは上の記事に書いてあるので、まずはこれを読んでください。
サンプルコード
僕がスクリプトを書くときのの雛形は大体以下の様な感じ。
#!/usr/bin/env perl
=head1 DESCRIPTION
サンプルです
=head1 SYNOPSIS
% sample.pl
=cut
use strict;
use warnings;
use utf8;
use Getopt::Long qw/:config posix_default no_ignore_case bundling auto_help/;
use Pod::Usage qw/pod2usage/;
# ハッシュで受ける
GetOptions(
\my %opt, qw/
foo=i
var=s
all
/) or pod2usage(1);
# 必須オプションのチェックとか
my @required_options = qw/foo/;
pod2usage(2) if grep {!exists $opt{$_}} @required_options;
# 処理
my $app = MyApp->new(%opt);
...
podとpod2usage
pod書いておくと、pod2usageで出てくるし便利。上の方にpodを書いているのは、仕事で使うツールだと、先に説明と使い方を書いておいたほうがすぐ使えるし分かりやすいという判断。
posix_default no_ignore_case
上のtagomorisさんの記事を熟読のこと
gnu_compatは指定不要
configにgnu_compatは指定していません。上記記事では、「—option=value といった、これはこれで馴染みがある形式の指定がデフォルトでは無効のため、
」と書いてありますが、これは勘違いで、—option=valueの形はデフォルトで有効です。
じゃあ、gnu_compatは何なのかというと、—option=valueでvalueに空文字列を指定し たいときに—option= とかやると、エラーになるのを回避してくれるというオプションです。
bundling
引数をまとめられるのは嬉しい。
それよりも個人的に嬉しいのは、このオプションを指定すると、ロングオプションを 必ずハイフン2つで始めなくてはいけなくなるということ。
例えば、上で allというオプションがあるが、デフォルトだと-allでも有効に なってしまうが、bundlingを指定すると—allじゃないと有効にならない。
auth_help
—helpとかした場合に、pod2usage出力を呼んで処理を抜けてくれる。
pod2usage(0) if $opt{help};
とか書かなくて良くなるのが微妙に便利。
同様にauto_versionてのもあって、—versionのハンドリングを自動でやってくれる。 $main::VERSIONが指定してあったらそれがバージョン番号として使われる。
これもちょっと便利だけど、普段は必要ないなーという感じ。
GetOptionsはハッシュで受ける
変数ではなくハッシュで受けてるのは、上記のように必須オプションのチェックがし やすいし、上記のようにそのまま引数として使えるのでスッキリして良いんじゃないかと思っている。
また、%optをさらにData::Validatorに掛けると良いんじゃないかと、typesterが言っていました。そういうこともやりやすくなります。
以前は変数で受ける派でしたが、typesterの影響でハッシュで受ける派になりました。
GetOptionsでエラーが有った際に処理を中断するように or pod2usage(2);
とやるのは定石。
pass_through
上記では指定していないし、普段は指定しないが、GetOptionsを複数回指定したい時などに使うのがpass_throughオプション。
通常@ARGVに余計な引数があれば警告を出してGetOptionsも偽値を返すが、それを無効にするオプション。
foo=var --all -- hoge=fuga
のように引数を--
で区切れば、順番に処理をすることも可能ですが、必要な引数がごちゃまぜになっている場合はうまくいかないので、pass_throughを指定します。
ままあるのが、Plackアプリを作って起動スクリプトを作った時に、port指定やらサーバー指定やらも混在してしまう場合。
まあ、書いててもなんのことやらだと思うので、拙作Plack::App::Directory::Markdownに同梱してあるpad-markdown.plのソースを一部抜粋して説明に変えます。
use Getopt::Long qw/GetOptions :config auto_help pass_through/;
...
GetOptions(\my %options, qw/
root=s
encoding=s
title=s
tx_path=s
markdown_class=s
markdown_ext=s
/) or pod2usage(2);
my $app = Plack::App::Directory::Markdown->new(%options)->to_app;
push @ARGV, '--port=9119' unless grep {/^(?:--port|-p)\b/} @ARGV;
my $runner = Plack::Runner->new;
$runner->parse_options(@ARGV);
$runner->run($app);
ここでは、GetOptionsを2回読んでいるわけではありませんが、$runner->parse_options(@ARGV)
がGetOptions同様の処理を行なっています。
configが上の説明とだいぶ違いますが、これを書いた頃はベタープラクティスが固まってなかったので、ご容赦下さい。