このすみ技術ろぐ

とあるWebエンジニアが、技術や趣味について書くブログです。

正規表現の後読みと先読みについて理解したので、紹介してみる

詳説正規表現を読み終わったのですが、正規表現には後読み先読みがあることを知りました。 特定条件の文字列を置換したいケースなどで、役に立つ機会がありそうです。

せっかく学んだので、紹介してみます。

詳説 正規表現 第3版

詳説 正規表現 第3版

先読み

正規表現の先読みは、(?=...)という構文を使います。

#!/usr/bin/perl

my $a = "abcdef";
my $b = "abc123";

# abcの右側に123が続く文字列かどうか
# マッチしない
if ($a =~ m/abc(?=123)/) {
    print "a is match !\n";
}

# abcの右側に123が続く文字列かどうか
# マッチするので、「b is match !」と出力される
if ($b =~ m/abc(?=123)/) {
    print "b is match !\n";
}

先読みの何が良いのか

単にabc123にマッチさせたいのであれば、/abc123/と書けば済む話です。 それでは何が異なるのかという話ですが、正規表現の先読みには、マッチした先読み対象の文字列を消費しないという最大の特徴があります。

(?=...)という先読み構文は、右側(直後)に先読み対象が存在する位置にマッチします。 マッチよりも置換で使うケースのほうがわかりやすいので、置換正規表現の例を紹介します。

#!/usr/bin/perl

my $a = "abc123abc122";
my $b = "abc123abc122";

# abcをdefに置き換える
$a =~ s/abc/def/g;
print $a."\n"; # def123def122

# 右側(直後)に123が続くabcを、defに置き換える
$b =~ s/abc(?=123)/def/g;
print $b."\n"; # def123abc122
# 先読み対象の123は、置換されないという点が重要です

後読み

正規表現の後読みは、(?<=...)という構文を使います。

#!/usr/bin/perl

my $a = "abc123";
my $b = "def123";

# 123の左側にdefがある文字列かどうか
# マッチしない
if ($a =~ m/(?<=def)123/) {
    print "a is match !\n";

}

# 123の左側にdefがある文字列かどうか
# マッチする
if ($b =~ m/(?<=def)123/) {
    print "b is match !\n";
}

後読みの何が良いのか

正規表現の後読みにおけるメリットは、先読みと同様です。

(?<=...)という後読み構文は、左側(直前)に後読み対象が存在する位置にマッチします。

#!/usr/bin/perl

my $a = "123abc122abc";
my $b = "123abc122abc";

# abcをdefに置き換える
$a =~ s/abc/def/g;
print $a."\n"; # 123def122def

# 左側(直前)に123があるabcを、defに置き換える
$b =~ s/(?<=123)abc/def/g;
print $b."\n"; # 123def122abc

先読みの否定構文

先読みの否定は、(?!...)を使います。

#!/usr/bin/perl

my $a = "abc";
my $b = "bac";
my $c = "cba";

# aの右側にbが続かなければマッチする
# マッチしない
if ($a =~ m/a(?!b)/) {
    print "a is match !\n";
}

# aの右側にbが続かなければマッチする
# マッチする
if ($b =~ m/a(?!b)/) {
    print "b is match !\n";
}

# aの右側にbが続かなければマッチする
# マッチする
if ($c =~ m/a(?!b)/) {
    print "c is match !\n";
}

後読みの否定構文

後読みの否定は、(?<!...)を使います。

#!/usr/bin/perl

my $a = "abc";
my $b = "bac";
my $c = "cba";

# bの左側にaがなければマッチする
# マッチしない
if ($a =~ m/(?<!a)b/) {
    print "a is match !\n";
}

# bの左側にaがなければマッチする
# マッチする
if ($b =~ m/(?<!a)b/) {
    print "b is match !\n";
}

# bの左側にaがなければマッチする
# マッチする
if ($c =~ m/(?<!a)b/) {
    print "c is match !\n";
}

まとめ

正規表現の先読みと後読みをまとめます。

タイプ 構文 説明
先読み (?=...) 左側(直前)に対象があればマッチする
後読み (?<=...) 右側(直後)に対象があればマッチする
先読みの否定 (?!...) 左側(直前)に対象がなければマッチする
後読みの否定 (?<!...) 右側(直後)に対象がなければマッチする

正規表現としてはマッチさせたいが、置換対象の文字列にはしたくないケースで使うと、とくに便利そうです。

先読み後読みをはじめとする、位置にマッチする正規表現を使えるようになると、正規表現中級者への階段を登れそうな気がします。 私も使いこなせるようになりたいです。

補足事項

本記事のプログラムはPerlで記述しておりますが、プログラミング言語によっては対応していなかったり、違う構文を採用している可能性があります。

実際に正規表現として使う場合は、必ず対象プログラミング言語のリファレンスを確認してから、使うようお願い申し上げます。

詳説 正規表現 第3版

詳説 正規表現 第3版