getopt の裏技的使い方
時々、他人の書いたソースコードをメンテナンスすることがあります。
で、最近実際にメンテする機会があったのですが、そのコードがオプション解析を自前でやってました。気味が悪いので、Getopt::Std あたりを使うように改めたいと思います。
(ちょっと古くさいコードだったので、ついでに近代化もプラスして。)
まず、元のコードはすげー単純化すると大体こんな感じ。(手元にないからちょっと違ってたかも)
#!/usr/bin/perl -w $a_str = undef; read_args(); print "-a オプションで渡された値 : $a_str\n" if ( defined $a_str ); # 本当は main な処理が続く... sub read_args { while( $_ = shift @ARGV ) { if ( $_ =~ /^-h$/ ) { usage(); } elsif ( $_ =~ /^-a$/ ) { $a_str = shift @ARGV; if ( ! defined $a_str ) { usage(); } } } } sub usage { print "my_prog.pl [-h] [-a somestring]\n"; exit; }
実行するとまあだいたいこんな感じ。
tsucchi@over[111]% ./my_prog.pl -h my_prog.pl [-h] [-a somestring] tsucchi@over[113]% ./my_prog.pl -a my_prog.pl [-h] [-a somestring] tsucchi@over[114]% ./my_prog.pl -a hoge -a オプションで渡された値 : hoge
-h オプションを渡した際に usage() を呼び、-a オプションの引数が足りない場合も usage() を呼びます。
で、これを getopt でなんとかしようと思ったら、結構めんどかった。分かっちゃえばなんてことはないのだけど、getopt ってドキュメント少な過ぎ...
いろいろ苦労した結果、こんな感じになりました。
#!/usr/bin/perl use strict; use warnings; use Getopt::Std; my $a_str; read_args(); print "-a オプションで渡された値 : $a_str\n" if ( defined $a_str ); # 本当は main な処理が続く... sub read_args { my %opts; getopt("ha", \%opts); for my $opt (keys %opts) { if ( $opt eq 'h') { usage(); } elsif ( $opt eq 'a' && defined $opts{$opt} ) { $a_str = $opts{$opt}; } else { usage(); } } } sub usage { die "my_prog.pl [-h] [-a somestring]\n"; }
getopt/getopts のグローバル変数 $opt_* だと「オプションが指定されていないから undef」なのか、「オプションの値がないから undef」なのか、うまく区別する方法がないみたいです。
getopt にハッシュのリファレンスを渡すと、
- オプションがないときはハッシュのキーがない
- オプションがあるけど、文字列がないときは、ハッシュの値に undef が入る
- オプションがあって、文字列が指定されると、ハッシュにその文字列が入る
という動きをしてくれるみたいです。なので、上記のコードでいけるわけ。
getopt のドキュメント化されていない仕様だと思うので、ご利用は計画的に。
それから、レガシーコードを大きくいじる場合は、テストコードを書かないと簡単に死ねますのでご注意を。