|
|
Perl正規表現の使い方 マッチした部分文字列の取得 |
H.Kamifuji . |
ここまでは対象の文字列が正規表現にマッチするかどうかを判別していましたが、ここではマッチした場合に、実際にマッチした部分文字列を取得する方法を確認します。 当ページでは、Linux CentOS7 の Gnome で動作テストしています。 |
|
対象の文字列の中でパターンにマッチする部分を取得する方法を確認します。 具体的な例で考えてみます。 my $str = "book is 2000yen, cake is 800yen"; if ($str =~ /\d+yen/){ print "$str\n"; }上記の場合、数値で構成される文字に「yen」が続く文字列にマッチします。この時マッチに成功すると自動的に特別な変数である「$&」にマッチした文字列全体が格納されます。 $& マッチした文字列が格納される今回の場合は「2000yen」にマッチします。よって変数「$&」には「2000yen」が格納されます。なお今回の場合は「2000yen」以外に「800yen」にもマッチしますが最初にマッチが成功した時点で結合演算子(=~)は評価が終わりますので「2000yen」が格納されることになります。 変数「$&」が次に書き換えられるまでマッチした文字列が格納されていますので、変数の値を表示したり他の変数に値を格納したりすることが出来ます。 my $str = "book is 2000yen, cake is 800yen"; if ($str =~ /\d+yen/){ print "マッチした文字列 : $&\n"; }なお「$&」は処理効率が悪いと言われていますので、可能であれば後のページで解説する「$1」などを使用して下さい。 では簡単なプログラムで確認して見ます。 test1-1.pl サンプルプログラム下記のサンプルを実行してみよう。# マッチした文字列全体を取得($&) use strict; use warnings; use utf8; binmode STDIN, ':encoding(cp932)'; binmode STDOUT, ':encoding(cp932)'; binmode STDERR, ':encoding(cp932)'; &check("book is 2000yen, cake is 800yen"); &check("orange is 950yen"); sub check{ my ($str) = @_; if ($str =~ /\d+yen/){ print "対象文字列: $str\nマッチ部分: $&\n\n"; } }上記を「test1-1.pl」の名前で保存してから次のように実行して下さい。 ![]() Linux 環境での実行結果は、下記です。シフトJIS で出力されるので nkf -w で UTF-8 に変換しています。 [xxxxxxxx@dddddddddd Ref]$ perl test1-1_u.pl | nkf -w 対象文字列: book is 2000yen, cake is 800yen マッチ部分: 2000yen 対象文字列: orange is 950yen マッチ部分: 950yen [xxxxxxxx@dddddddddd Ref]$ |
前のページにてパターンにマッチした部分を取得しましたが、マッチが成功した時にマッチした部分よりも前の部分と後の部分をそれぞれ取得することが可能です。 $` マッチした部分よりも前の部分が格納される $' マッチした部分よりも後の部分が格納される具体的な例で考えてみます。 my $str = "book is 2000yen, cake is 800yen"; if ($str =~ /\d+yen/){ print "マッチした文字列 : $&\n"; }マッチが成功した場合、自動的に変数「$&」にはマッチした文字列全体が格納されます。今回の場合は「2000yen」です。そして同じように特別な変数「$`」にはマッチした文字列より前の部分「book is 」が格納され、また特別な変数「$'」にはマッチした部分より後の部分「, cake is 800yen」が格納されます。 変数「$`」と変数「$'」は次に書き換えられるまでマッチした文字列が格納されていますので、変数の値を表示したり他の変数に値を格納したりすることが出来ます。 my $str = "book is 2000yen, cake is 800yen"; if ($str =~ /\d+yen/){ print "前の部分 : $`\n"; print "マッチした文字列 : $&\n"; print "後の部分 : $'\n"; }なお「$&」同様「$`」と「$'」も処理効率が悪いと言われています。 では簡単なプログラムで確認して見ます。 test2-1.pl サンプルプログラム下記のサンプルを実行してみよう。# マッチした文字列の前後を取得($`, $') use strict; use warnings; use utf8; binmode STDIN, ':encoding(cp932)'; binmode STDOUT, ':encoding(cp932)'; binmode STDERR, ':encoding(cp932)'; &check("book is 2000yen, cake is 800yen"); &check("orange is 950yen"); &check("3500yen?"); sub check{ my ($str) = @_; if ($str =~ /\d+yen/){ print "<$`><$&><$'>\n"; } }上記を「test2-1.pl」の名前で保存してから次のように実行して下さい。 ![]() 今回のサンプルでは、マッチした部分、そしてその前後をそれぞれ「<」と「>」で囲んで表示しています。 Linux 環境での実行結果は、下記です。シフトJIS で出力されるので nkf -w で UTF-8 に変換しています。 [xxxxxxxx@dddddddddd Ref]$ perl test2-1_u.pl | nkf -w <book is ><2000yen><, cake is 800yen> <orange is ><950yen><> <><3500yen><?> [xxxxxxxx@dddddddddd Ref]$ |
対象の文字列に対してパターンがマッチした場合に、パターンの中の一部分を括弧()で括ることで、括弧内のパターンにマッチした部分文字列を取得することが出来ます。 /\d+yen/上記のようなパターンがあった場合に、「\d+」及び「yen」にそれぞれマッチする部分を分けて取得してみます。 /(\d+)(yen)/上記は「\d+yen」と言うパターンに対して「\d+」と「yen」をそれぞれ括弧()で括っています。マッチが成功すると最初の括弧で囲まれたパターンにマッチした部分が特別な変数「$1」に格納され、次の括弧で囲まれたパターンにマッチした部分が変数「$2」に格納されます。 括弧内のパターンにマッチした値は特別な変数「$1」「$2」..に格納される次の例を見てください。対象の文字列が「book is 2045yen」の場合にパターンを「(\d+)(yen)」としています。 my $str = "book is 2045yen"; if ($str =~ /(\d+)(yen)/){ print "$1\n"; print "$2\n"; }上記の場合、パターン全体は「2045yen」にマッチします。マッチに成功した時、最初の括弧内に記述されたパターン「\d+」にマッチする「2045」が変数「$1」に格納されます。また、次の括弧内に記述されたパターン「yen」にマッチする「yen」が変数「$2」に格納されます。 今回は2つ括弧を使用していますが3つ以上の括弧を記述した場合は「$3」「$4」といった変数に格納されていきます。 $&の代わりにマッチした全体を取得する前述したとおり変数「$&」を使用すると若干処理効率が悪いと言われています。そこで変数「$&」を使用する代わりにパターン全体を()で囲うことで「$&」の代わりに「$1」を使用することが出来ます。my $str = "book is 2045yen"; if ($str =~ /(\d+yen)/){ print "$1\n"; } 括弧と変数の関係マッチした文字列は順に「$1」「$2」と格納されていきます。格納される順番はパターンの中で「(」が現れた順となります。/(\d+)(yen)/ $1 \d+ $2 yenまたパターンの中で括弧()は重複した範囲を括ることが出来ます。例えば次のような場合です。 /((\d+)(yen))/この場合、格納される変数と対応するパターンの関係は次の通りです。 /((\d+)(yen))/ $1 <-> \d+yen $2 <-> \d+ $3 <-> yenパターンの中で最初に現れる「(」はパターン全体を括っています。2番目の「(」は「\d+」を、3番目の「(」は「yen」を括っています。よって各変数に対応するパターンは上記のようになります。対象文字列が「book is 2045yen」だった場合、変数に格納される値は次の通りです。 /((\d+)(yen))/ $1 <= 2045yen $2 <= 2045 $3 <= yenでは簡単なプログラムで確認して見ます。 test3-1.pl サンプルプログラム下記のサンプルを実行してみよう。# マッチした複数の文字列を取得($1, $2, ..) use strict; use warnings; use utf8; binmode STDIN, ':encoding(cp932)'; binmode STDOUT, ':encoding(cp932)'; binmode STDERR, ':encoding(cp932)'; print "パターン: /((\\d+)(yen))/\n\n"; &check("book is 2000yen, cake is 800yen"); &check("orange is 950yen"); sub check{ my ($str) = @_; if ($str =~ /((\d+)(yen))/){ print "対象: $str\n"; print "\$1: $1\n"; print "\$2: $2\n"; print "\$3: $3\n\n"; } }上記を「test3-1.pl」の名前で保存してから次のように実行して下さい。 ![]() Linux 環境での実行結果は、下記です。シフトJIS で出力されるので nkf -w で UTF-8 に変換しています。 [xxxxxxxx@dddddddddd Ref]$ perl test3-1_u.pl | nkf -w パターン: /((\d+)(yen))/ 対象: book is 2000yen, cake is 800yen $1: 2000yen $2: 2000 $3: yen 対象: orange is 950yen $1: 950yen $2: 950 $3: yen [xxxxxxxx@dddddddddd Ref]$ |
パターン内で括弧を使用してマッチする部分文字列を取得する場合、繰り返しを表すメタ文字と組み合わせて使用する際に括弧の位置によって結果が異なる場合があります。 メタ文字「+」まずはメタ文字の「+」を使用する場合です。/(\d+)yen/「\d+」は数字が1回以上繰り返された場合にマッチします。そして「\d+」全体が括弧()で括られていますので、このパターンがマッチした場合、変数「$1」には「\d+」にマッチした全体が格納されます。 my $str = "book is 2391yen, cake is 800yen"; if ($str =~ /(\d+)yen/){ print "$1\n"; }上記の場合ですと、変数「$1」には「2391」が格納されます。 今度は次のようにパターンを記述してみます。 /(\d)+yen/上記の場合ですと、変数「$1」には「1」が格納されます。これは「2」「3」「9」「1」と順に変数「$1」に格納されていき、最後に格納された「1」が残っているためです。 メタ文字「?」とメタ文字「*」次にメタ文字の「?」と「*」の場合です。基本的にはメタ文字の「+」の場合と同じですが異なるのは0回マッチも許されている点です。まず繰り返しを表すメタ文字が括弧の中にある場合です。 /(\d*)yen/この場合、変数「$1」には括弧で括られた「\d*」にマッチした文字列が格納されます。1回以上マッチした場合はメタ文字(+)の場合と同じですが、0回マッチした場合には「\d*」は空文字にマッチしたことになるため変数「$1」には空文字が格納されます。 次に繰り返しを表すメタ文字が括弧の外にある場合です。 /(\d)*yen/この場合、変数「$1」には「\d」にマッチした文字格納されますが、「\d」に一度もマッチしまなかった場合は変数「$1」には値が一度も格納されず未定義値(undef)となります。ただし「(\d)」に対してメタ文字(*)が設定されていますのでパターン全体としてマッチするかどうかに影響ありません。 このようにメタ文字の「*」や「?」を使用する場合に一度もマッチしない場合の挙動が若干異なります。 では簡単なプログラムで確認して見ます。 test4-1.pl サンプルプログラム下記のサンプルを実行してみよう。# 括弧()の位置による取得文字列の違い use strict; use warnings; use utf8; binmode STDIN, ':encoding(cp932)'; binmode STDOUT, ':encoding(cp932)'; binmode STDERR, ':encoding(cp932)'; print "パターン: /(\\d+)yen/\n"; &check1("book is 2391yen, cake is 800yen"); print "\n"; print "パターン: /(\\d)+yen/\n"; &check2("book is 2391yen, cake is 800yen"); sub check1{ my ($str) = @_; if ($str =~ /(\d+)yen/){ print "対象: $str\n"; print "\$1: $1\n"; } } sub check2{ my ($str) = @_; if ($str =~ /(\d)+yen/){ print "対象: $str\n"; print "\$1: $1\n"; } }上記を「test4-1.pl」の名前で保存してから次のように実行して下さい。 ![]() Linux 環境での実行結果は、下記です。シフトJIS で出力されるので nkf -w で UTF-8 に変換しています。 [xxxxxxxx@dddddddddd Ref]$ perl test4-1_u.pl | nkf -w パターン: /(\d+)yen/ 対象: book is 2391yen, cake is 800yen $1: 2391 パターン: /(\d)+yen/ 対象: book is 2391yen, cake is 800yen $1: 1 [xxxxxxxx@dddddddddd Ref]$ |
パターン内で括弧を使用することで、括弧内のパターンにマッチする部分文字列が記憶されます。記憶された部分文字列はパターンマッチが完了すると変数「$1」などに格納されプログラムの中で利用することが出来ます。 このパターンにマッチし記憶された部分文字列をパターン内でも参照することが可能です。このように記憶された部分文字列をパターン内で参照することを後方参照と呼んでいます。記憶された部分文字列は最初に記憶された内容は「\1」、次に記憶された内容は「\2」のように参照することが可能となります。 記憶された値はパターン内で「\1」「\2」..で参照できるでは具体的な例として<h1>で始まる場合は</h1>までを、<h2>で始まる場合は</h2>までをマッチさせる正規表現を考えてみます。 /<(h1|h2)>.*<\/\1>/上記のパターンではまず「<(h1|h2)>」の部分で「<h1>」又は「<h2>」のどちらかにマッチします。この時マッチした「h1」又は「h2」のどちらかが記憶されます。「.*」の部分で任意の文字にマッチします。そして「<\/\1>」の中の「\1」の部分は既に記憶された文字列が参照されます。つまり「h1」が記憶されていれば「<\/h1>」となり「h2」が記憶されていれば「<\/h2>」にマッチします。(「/」はエスケープが必要なので「\/」となっています)。 このように既にマッチした内容を使ってパターンそのものを記述する場合に使用します。 では簡単なプログラムで確認して見ます。 test5-1.pl サンプルプログラム下記のサンプルを実行してみよう。# 後方参照によるパターン内での参照(\1, \2, ..) use strict; use warnings; use utf8; binmode STDIN, ':encoding(cp932)'; binmode STDOUT, ':encoding(cp932)'; binmode STDERR, ':encoding(cp932)'; print "パターン: /<(div|span)>.*\\1>/\n\n"; &check("book is上記を「test5-1.pl」の名前で保存してから次のように実行して下さい。 ![]() Linux 環境での実行結果は、下記です。シフトJIS で出力されるので nkf -w で UTF-8 に変換しています。 [xxxxxxxx@dddddddddd Ref]$ perl test5-1_u.pl | nkf -w パターン: /<(div|span)>.*</\1>/ 対象: book is <div>2391yen</div>, cake is 800yen <div>2391yen</div> 対象: today is <span>Friday</span> <span>Friday</span> [xxxxxxxx@dddddddddd Ref]$ |
|