正規表現(5) キャプチャ

正規表現の中でカッコ ( ) を使うと、マッチした文字列の一部をキャプチャ(捕獲)して後で使う事ができます。これは、正規表現を使う上でとっても重要なテクニックです。
例えば

m/aaa(bbb)ccc/

というパターンがあるとします。この検索パターンは「aaabbbccc」という文字列にマッチします。カッコは特殊な文字なので、検索する時は無いモノとして扱われるのです。つまり「aaa(bbb)ccc」という文字列にはマッチしません。*1

"aaabbbccc"   =~ m/aaa(bbb)ccc/;  # 真:マッチする
"aaa(bbb)ccc" =~ m/aaa(bbb)ccc/;  # 偽:マッチしない 

このパターンの場合、「bbb」をキャプチャしている事になります。キャプチャした文字列は左のカッコから順番に $1、$2、$3、$4… という連番の変数に入ります。
例えば、以下の場合

"abcdef" =~ m/a(b)c(d(e)f)/;  # マッチする
print "$1\n$2\n$3\n";

実行すると

b
def
e

と表示されます。$1に「b」が、$2に「def」が、$3に「e」が入っていますね。(^_^)

このキャプチャを利用すると、substr 関数などを使わなくても、簡単に文字列の一部を取り出す事ができます。(ただし、「パターン」を見つける必要があります)
例えば、Windows の設定ファイルの一種として INI ファイルというものがありますが、一行が「名前=値」という書式になっています。*2
この1行を取り出したとして、ここから名前と値を取り出したいとすると

use strict;
 
my $line = "Food=Apple";   # INIファイルから取り出した1
 
if ($line =~ m/([^=]+)=(.+)/) {
	print "Name : $1\n";
	print "Value: $2\n";
}

のようになります。
実行結果は以下の通り。

Name : Food
Value: Apple

とっても楽チンですね。(^_^)

使用した正規表現

m/([^=]+)=(.+)/

について詳しく説明すると、まず1つめのカッコ($1)が名前にあたり、「=」をはさんで、2つめのカッコ($2)が値にあたります。
「[^=]+」は、「= 以外の文字列」にマッチします。「[^=]」は「= 以外の何か1文字」、「+」は「直前の文字が、1個以上連続する」という意味ですから、「=」までの文字列(Food)にマッチします。(^_^)
「.」(ドット)は、「改行以外の何か1文字」にマッチします。「+」を付ける事で末尾までの文字列を取得しています。
「+」(1文字以上)、「*」(0文字以上連続)、「?」(0文字または1文字)のような「文字の連続量を表す」特殊文字の事を「量指定子」と呼びます。量指定子を使った正規表現は、できるだけ「長く」マッチしようとします。あればあるだけ飲み込んでしまう「欲張り」なマッチです。(^_^;)
これを「できるだけ短く」にするには、量指定子の後ろに「?」を付ければ良いそうです。

use strict;
 
"aaabbbaaabbbaaabbb" =~ m/.+b/;
print "$&\n";   # $& はマッチした文字列全体
 
"aaabbbaaabbbaaabbb" =~ m/.+?b/;
print "$&\n";

前者のパターンでは、「+」なのでできるだけ長くマッチします。後者は「+?」なのでできるだけ短くマッチします。
実行結果は以下の通り。

aaabbbaaabbbaaabbb
aaab

正規表現を使うと、文字列処理がとても簡単ですね。正規表現が無いプログラミング言語ではこうはいきません。
「名前=値」を扱うにも、まず「=」の位置を探して…「=」の前までの文字列を切り出して…「=」の後ろから末尾までの長さを求めて…「=」の後ろの文字列を切り出して…大変です。(^_^;
正規表現は工夫しだいで色々な事に使えそうですね♪

*1:ちなみに、カッコを普通の文字として扱いたい場合は、前に「\」(円記号)をつけてエスケープします。例:m/aaa\(bbb\)ccc/ → "aaa(bbb)ccc" にマッチします。

*2:厳密には、[Category] のようなカテゴリもあります。