正規表現のキャプチャの結果を取得したいときには、次のようなコードを書く:
if (my @capture = '10/17' =~ m!([0-9]{2})/([0-9]{2})!) { # @capture = (10, 17) }
$1や$2を使っても取得はできるが、キャプチャが増えたときに$1, $2, $3, $4, ...と増やしていくのは苦行である。
このようにすると、@captureにキャプチャの結果が入る。しかし、キャプチャの括弧がない正規表現の場合、@captureの値はどうなるのか。
if (my @capture = '10/17' =~ m![0-9]{2}/[0-9]{2}!) { # @capture = (1) }
キャプチャされていないのに、@captureには(1)が代入されている。空リストはfalse扱いなので、こういったケースのために(1)を返すみたいだ。
しかし、@captureは空リストであるべきなので、どうすればうまく解決できるか、Hokkaido.pm Casual#6で質問したところ、@jamadamさんが教えてくれた。@jamadam++
Marquee/lib/Marquee/Plugin/Router.pm at master · jamadam/Marquee · GitHub
if (my @captures = ($path =~ $regex)) { $cb->(defined $1 ? @captures : ()); last; }
$1に何も入っていない場合は、キャプチャしていないと判断して、空リストにしているみたいだ。でもちょっと待った。キャプチャされる場合とされない場合がある正規表現だと、どうなる?:
if (my @capture = $filename =~ /.+?(?:\.(.+))?$/) { @capture = () unless defined $1; # @capture is? }
この正規表現は以下の通りにマッチする。
- 'file.jpg'
- @capture = ('jpg');
- 'file.tar.gz'
- @capture = ('tar.gz');
ここまでは、問題なくキャプチャできる。しかし、この正規表現はキャプチャしない場合も考えられる:
- file
- @capture = ();
この場合、@captureが(undef)になるべきだと思うので、$1を使った方法ではうまく解決できないことが分かる。なぜ(undef)であるべきか:
if (my @capture = '10:12 rainy' =~ /(\d+):(\d+)(?::(\d+))?\s+(.+)/) { # @capture = (10, 12, undef, 'rainy') # この場合では、undefが代入されている! }
perldoc perlvarあたりが怪しいよね、と@aloelightさんが言っていたので、読みあさってみたところ特殊変数@+を発見した。
You can use $#+ to determine how many subgroups were in the last successful match.
$#+でキャプチャの数が分かるみたいだ。キャプチャの数が分かれば、あとは簡単である。
また、@-という特殊変数もあり、同じように$#-とするとキャプチャの数が分かる。$#-は、*マッチした*キャプチャの数を返し、$#+はマッチして(?:いる|いない)キャプチャの数を返すといった違いがあるみたいだ。
最終的に、今回の問題は$#+を使って、キャプチャの数を見て判別することにした。Perlの特殊変数は奥が深すぎて、大半の特殊変数は知らないものばかりですね :)
if (my @capture = $filename =~ /.+(?:\.(jpe?g|png|gif))?/) { @capture = () unless $#+; # @capture is? }
- file.jpeg
- @capture = ("jpeg");
- file.png
- @capture = ("png");
- file
- @capture = (undef);
追記
最後の例ですが、/.+?(?:\.(.+))?$/
に書き直そうとして忘れてました。もし、そのままの形で修正するとなると、/.+?(?:\.(jpe?g|png|gif))?$/
となりますね。(thanks kitsさん: /.+(?:\.(jpe?g|png|gif))?/
は .+
で文字列の末尾までマッチするので、()
部分では何もキャプチャされない。)
()?
を使わないほうがいい、というのはおっしゃる通りです。実際、使ったことないです :)