Top > MyPage
 

正規表現入門

はじめに

正規表現は詳細に書くと、1冊の本が書けてしまいます(現に、O'REILLY社の「詳細 正規表現」など色々本が出版されています)。

詳しく知りたい場合は、或いは一からステップアップしたい方はそれらの本を参照していただくとして、ここでは基本的なところから一気に上級編へとダイブします。またここでは、多少言語によって癖のようなものがありますので、Perl限定)とします。

そもそもこの正規表現を使うことによるメリットは何でしょうか。 [<ol></ol> is illegal in <p>] などが、あげられるかもしれません。さてさて、正規表現の扉を開けましょう。

マッチさせるかたち

通常ある文字列とマッチング(含まれるかどうか、というような意味)したいと考えたとき、簡単には

if ($text eq 'exit') {
  ....
}

というように、文字列が同じかどうかを判断することは可能です。しかし、対象の文字列が長く、その中の一部とマッチングしたい場合はこのeqでは難しいでしょう。

こういうときに正規表現が活きてきます。Perlでは一般的に、正規表現は次のように表します。

$text =~ m/exit/;

のように、=~という演算子とm/.../という表現で表します(正確には..のところが正規表現ですね、たぶん)。つまり上記は「$textにexitが含まれているか」というような意味になります。ただし、m/.../mは省略できるので、

$text =~ /exit/;

と表すことも出来ます。通常Perlではこういう風に書き表される正規表現ですが、ある意味「なんでもアリ」のPerlでは他にも書き方が用意されています。特に、ベストプラクティスという本で推奨されているのは

  $text =~ m{exit};

です(実際はm/ .. /と上記以外は使うな、と述べられています)。なので、この文章の中では上記を使うようにしていきます。

置換の場合のかたち

基本的には「マッチ」と同じですが、変換の文字列を指定するところが違います。

$text = "This is Sakai speaking.";
$text =~ s{Sakai}{Chiku};
print $text,"\n";

というように、先ほどのマッチングの時の後ろに、置換文字列を指定します。もちろん、

$text = "This is Sakai speaking.";
$text =~ s/Sakai/Chiku/;
print $text,"\n";

でもOKです。

正規表現修飾子

正規表現の動作自体を変更するのに使われるもので、次のようなものがあります。

正規表現修飾子
修飾子 説明
i アルファベットの大文字・小文字を無視する動きになります。
x スペースとコメントを自由に挿入できるようになります(その分スペースをそのまま入れられなくなります)
s 通常、後述するメタ文字の . は改行文字にはマッチしませんが、これを指定するとマッチするようになります。
m これも後述する$などが、通常は文字列の最後にマッチしますが、これを指定すると、文字列の途中に改行文字があった場合、その直前にマッチするようになる。
o これを指定すると、正規表現のコンパイルが1回限りしか行わなくなる。もともと正規表現の中に変数がない場合は変わらないが、もし変数があるにせよ、その変数が動的に変わらない場合はパフォーマンスが上がることが期待される。
g 置換の場合は、マッチする文字は全て置換します(指定しない場合は、置換は最初のものだけです)。また、マッチングの時は、特に while 文なので、マッチを繰り返します。

i

これを指定しない場合は、

$text = "Sakai is a good guy!";

if ( $text =~ m{sakai} ) {
    print "matched!\n";
}
else {
    print "not matched!\n";
}

のようにマッチしません(not matched!が出力される)。しかし、

$text = "Sakai is a good guy!";

if ( $text =~ m{sakai}i ) {
                     #^
    print "matched!\n";
}
else {
    print "not matched!\n";
}

のようにiを指定するとマッチします(matched!が出力される)。

x

とかく、正規表現は煩雑になり、コードとして読みにくくなります。例えば(意味は無視してください)

    while ( $line =~ m{(?:^|\t)(?:"((?:[^\t]|"")*)"|([^\t])*)}g ) {
      #.....
    }
}

は、何とマッチさせようとしているのかが、にわかにはわかりません。これを

    while (
        $line =~ m{
                       (?:^|\t)            #文字列先頭か、タブで始まる
                       (?:                 #文字をまとめる(がキャプチャしない)
                           "               #ダブルクォーテーションがあって
                           ((?:[^\t]|"")*) #タブ以外かダブルクォーテーションが2つ続く
                           "               #ダブルクォーテーションで閉じる
                           |               #上記か、それとも
                           ([^\t])*        #ダブルクォーテーション以外が続く
                       )
                    }gx
          ) {
            #.....
          }

のように正規表現の中にスペースや改行、コメントなどを入れられると可読性が高まります。複雑な正規表現を使う場合は推奨されます。

s,m

これら2つは少々詳しく説明が必要です。Perlのお父さん・お爺さんにあたるGREPやAWKというプログラムは、ファイルを1行ずつ読み取り、その中で正規表現を使って、なんらかの処理をほどこすというような性格のプログラムでした。

Perlも同様なことは、もちろん出来ます。例えば、ファイルから1行ずつ読み出して、Sakaiが含まれる行を出力するには、

while (<>) {
    if ( $_ =~ m{Sakai} ) {
        print $_;
    }

という短いプログラム(hoge.pl)をsakai.txt(中に文章があって、そこにSakaiが含まれる行が存在するとして)

perl hoge.pl sakai.txt

と実行すると、Sakaiが含まれる行が出力されます。

こう書いてくると、まるでPerlにもという概念があるように思えてきます。しかし、それは「この場合」という限定がつきます。つまり、while(<>)は、コマンドラインの引数のファイルを1行ずつ読み込んで、その行のテキストを$_に入れてくれるので、結果的には行毎に処理が行われることになるからです。じゃあ、その1行ずつということをPerlはどのように判断しているかというと、$/というグローバル変数には通常'\n'が入っており、LFが行のセパレータになっていることになります。ただ、この辺はちょっと込み入っており、ここなどを読んで、知っておく必要があるかもしれません。ちょっとだけ引用すると

  • In most operating systems, lines in files are terminated by newlines. Just what is used as a newline may vary from OS to OS. Unix traditionally uses \012, one type of DOSish I/O uses \015\012, and Mac OS uses \015.

    Perl uses \n to represent the "logical" newline, where what is logical may depend on the platform in use. In MacPerl, \n always means \015. In DOSish perls, \n usually means \012, but when accessing a file in "text" mode, STDIO translates it to (or from) \015\012, depending on whether you're reading or writing. Unix does the same thing on ttys in canonical mode. \015\012 is commonly referred to as CRLF.

とあり、要するにMACを除いて、通常は他のOSでは\nはLFを意味するが、DOSの場合はファイルを1行ずつ読み込むような場合だけは、CRLFとして扱うということです。

さて、話を戻すと、ファイルを1行ずつ読み込むような場合は1行ずつ処理するという考え方になりますが、正規表現ではデフォルトでは行という概念はありません。つまり

$text = <<'EOB';
1st line
2nd line
3rd line
4th line
EOB

if ( $text =~ m{^([\w\d\s]+)line}) {
    print $1, "\n";
}

を実行すると、何が([\w\d\s]*)にマッチするかというと(メタ文字等は下記を参照)、

1st line
2nd line
3rd line
4th 

が、出力されます。つまり、途中の改行も含めて最後の

4th line
^^^^

までマッチしたことになります。それは\sに改行も含まれるので、「最初から文字か数字か\sが1個以上続きlineで終わるもの」をマッチしようとすると、1stからマッチングが始まって、最後のlineでマッチングが終了したわけです。では、

$text = <<'EOB';
1st line
2nd line
3rd line
4th line
EOB

if ( $text =~ m{^([\w\d ]+)line$}) {
    print $1, "\n";
}

ですと、1つもマッチしません。それは何故かといいますと、^[\w\d ]+$で「最初から、文字と数字と半角スペースのいずれかが1個以上続き最後にlineとあるもの」ということを意味しますが、その最後の意味が行の最後ではなく、文字列全体の最後を意味するからです。そして、文字列の最後までに[\w\d ]以外に改行文字も含まれているので、マッチに失敗するというわけです。

ようやく、結論に近づいてきました。つまり、通常のモードでは^$はそれぞれ、文字列の最初、最後を意味するのですが、m修飾子をつけると、その意味が変わります。つまり^文字列の先頭か、改行文字の直後にマッチし、$文字列の最後か、改行文字の直前にマッチするようになります。さらに言い換えると、^$行頭、行端にマッチするようになるということになります。

また、先述したように.は改行文字以外を意味しますが、s修飾子を付けると、改行文字も含むようになります。これらを実際に確認しますと

$text = <<'EOB';
1st line
2nd line
3rd line
4th line
EOB

while ( $text =~ m{^([\w\d ]+)line$}gm) {
    print $1, "\n";
}

ですと、m修飾子を付けたので、$が改行文字の前にもマッチしますので、1行ずつ処理する形になりますし、

$text = <<'EOB';
1st line
2nd line
3rd line
4th line
EOB

if ( $text =~ m{^(.+)line}s) {
    print $1, "\n";
}

sオプションをつけているので、最初から最後までマッチします。出力は、

1st line
2nd line
3rd line
4th 

になります。

o

これは正規表現内に変数を入れる際に、その変数が動的に変わらないようなときに有用です。例えば、次のようなtext.txtというファイルがあったとして、

"Hi!", I said to Mary.
But she did not answer.
I thought she might be angry then.

これに対して、testRegex.plという次のようなプログラムを

open( IN, "<", $ARGV[0] );
while (<IN>) {
    if ( $_ =~ m{$text}o ) {
        print $_;
    }
}

実行すると、

"Hi!", I said to Mary.

が出力されます。さて、問題は正規表現内のm{$text}という変数です。これは、プログラムを実行する際の引数で与えた(perlに対しては3つめ、プログラムに対しては2つめの引数)ものを格納している変数ですが、少なくともwhile(<>)内で、値が変わるわけではありません。

Perlは内部的には正規表現を、記述が正しいかどうかをチェックした後にコンパイルし、その後正規表現エンジンにそのコンパイルしたものを渡すらしいですが、変数を含んでいないような場合はキャッシュに保存していて再利用しますので大丈夫ですが、変数が含まれていると、毎回コンパイルしなおすので、結構パフォーマンスが落ちてしまいます。上記のような例のように、変数の中身が変わらないような場合は、このo修飾子を入れておくと、1回しかコンパイルしないので、パフォーマンスの向上が期待できます(そのループが長い場合には特に)。

perl 

g

置換の場合はわかりやすいと思います。つまり、全置換を表します。

$text = "This is a pen and this pen is not mine.";
$text =~ s{([Tt])his}{\1hat}g;
print $text,"\n";

That is a pen and that pen is not mine.

と出力されますが、これは、2つのthisをいっぺんに置換します。ちなみに\1は前の([Tt])his()の中、つまりTかt([..]は後から出てくる文字クラスで、その中の文字列のどれか、という意味)が入っています。

しかし、マッチングの場合はだいぶ意味が違います。

リストコンテキスト

Perlではコンテキストでメソッドの動作が変わることがよくあります。このgオプションを付けたとき、さらにグルーピングをしたときは、このリストコンテキストでは次のように振る舞います。

$text = << 'EOF';
僕は、1970年の万博には、当時和歌山に住んでいたので、
10回以上通い詰めた。その後1972年に茨城に引っ越し、
1973年には千葉に引っ越し、さらに1978年には大学の関係で、
再度茨城に引っ越すことになった。;
EOF

@match = $text =~ m{(\d+)}g;
print join( "|", @match ), "\n";

を実行すると、

1970|10|1972|1973|1978

が出力されます。つまりこのリストコンテキストでは、数字が続く部分が、何度もマッチングしてグルーピングされた部分がリストとなって返ってきます。

もし、gオプションがないと、もちろん

1970

が、出力されます。つまり、1度だけしかマッチングが行われないということです。

スカラーコンテキスト

スカラーコンテキストでは、動きが変わります。

$text = << 'EOF';
僕は、1970年の万博には、当時和歌山に住んでいたので、
10回以上通い詰めた。その後1972年に茨城に引っ越し、
1973年には千葉に引っ越し、さらに1978年には大学の関係で、
再度茨城に引っ越すことになった。;
EOF

$match = $text =~ m{(\d+)}g;
print "$1/$match\n";

$match = $text =~ m{(\d+)}g;
print "$1/$match\n";

$match = $text =~ m{(\d+)}g;
print "$1/$match\n";

$match = $text =~ m{(\d+)}g;
print "$1/$match\n";

$match = $text =~ m{(\d+)}g;
print "$1/$match\n";

$match = $text =~ m{(\d+)}g;
print "$1/$match\n";

$match = $text =~ m{(\d+)}g;
print "$1/$match\n";

を実行すると

1970/1
10/1
1972/1
1973/1
1978/1
1978/
1970/1

が出力されます。これは何を意味しているかと言いますと、スカラーコンテキストでは、まず返ってきている値はマッチに成功した場合には1、失敗した場合にはundefだということです。上記の最後から2番目には/1が出力されていないのはそのせいです。

また、マッチングした位置を覚えているということが重要です。つまり、最初の

$match = $text =~ m{(\d+)}g;
print "$1/$match\n";

で、マッチしたのが1970ですが、次の

$match = $text =~ m{(\d+)}g;
print "$1/$match\n";

では、さっきのマッチした位置の次からマッチングを開始するので、10がマッチングします。これがずっと続くのですが、最後から2番目の

$match = $text =~ m{(\d+)}g;
print "$1/$match\n";

でマッチングに失敗したので、undefが返され、そして先ほどまでのマッチした位置がリセットされます。したがって、最後の

$match = $text =~ m{(\d+)}g;
print "$1/$match\n";

では、最初の1970に戻っています。

これらを応用すると、while文でループすることが出来ます。

$text = << 'EOF';
僕は、1970年の万博には、当時和歌山に住んでいたので、
10回以上通い詰めた。その後1972年に茨城に引っ越し、
1973年には千葉に引っ越し、さらに1978年には大学の関係で、
再度茨城に引っ越すことになった。;
EOF

while ( $text =~ m{(\d+)}g ) {
    print "$1\n";
}

を実行すると、

1970
10
1972
1973
1978

が出力されます。

メタ文字

メタ文字は以下にもあるように、たくさんあります(実際には以下以外にもまだあります!)。一気に覚えるのは相当難しいと思われますが、どれも必須なので徐々に覚えていってください。本によってはメタ文字エスケープシーケンスを区別する場合もあるようですが、今回は全部ひっくるめてメタ文字として扱っておきます。

メタ文字
メタ文字 説明
.

任意の文字。改行文字を除く任意の1文字にマッチ

ただし、後述するパターンマッチ修飾子/sが使われる場合は改行も含めて全てにマッチします。

[a-z0-9]

[ ]の中の任意の1文字にマッチ(例では、小文字または数字の任意の1文字)

文字の間にハイフン( - )を挟み、文字列の範囲を表すことができます。たとえば、[12345]の様に規則的に連続した文字列は、[1-5]と表記できます。

[^a-z0-9]

[ ]の中にない任意の1文字にマッチ(例では、小文字または数字以外の任意の1文字)

チルダ( ^ )は必ずパターンの先頭に置きます。先頭以外にある^はリテラルと解釈されます(※大括弧の外では文字列の先頭を意味するので要注意)。

?

直前の文字か0個か1個存在する(1個までオプションとして許す)

DOSなどのワイルドカード(同じ?*)と違い、その文字だけで意味があるわけではない。前の文字の存在が前提となっている。

*,*?

*は直前の文字が0個以上続く(いくつでも許す)。ただし欲張りなので、可能な限りマッチしようとする。*?は非欲張りなので、自分自身のマッチングより、次のマッチングを優先する。

これも直前の文字が前提になっている。

+,+?

直前の文字が1個以上続く(1個は要求するが、それ以上はオプション)。ただし欲張りなので、可能な限りマッチしようとする。+?は非欲張りなので、自分自身のマッチングより、次のマッチングを優先する。

これも直前の文字が前提になっている。

{min,max},{min,max}?,{num},{num,},{num,}?

直前の文字などが続くことを、その個数を以上/以下で指定する。数字が1つの場合は、ちょうどその個数分続く。また、{num,}だと以上という意味になる。

?が付くと、その中でも最短(非欲張り)でマッチングする。もちろん{num}はちょうどその個数分なので、?は付かない。

\d

数字の1文字にマッチ

[0-9]と同じです。

\D 数字以外の1文字にマッチ [^0-9]と同じです。
\w

アルファベットまたは数字(単語)の1文字にマッチ

[a-zA-Z_0-9]と同じです。

\W

アルファベットと数字以外(単語以外)の1文字にマッチ

[^a-zA-Z_0-9]と同じです。

\n \n Macを除いて(MacではCR)、WINDOWS、LINUX、DOSなどではLFを意味する。
\r \r Macを除いて(MacではLF)、WINDOWS、LINUX、DOSなどではCRを意味する。
\f,\t \f は改ページ、 \t はタブを意味する。
\NNN,\xNN

\NNNのNNNには数字が入り、8進数を表し、\xNNは16進数を表す。

例えば、CR/LFは8進数で表すと\015\012、16進数では\x0D\x0Aと表記できる。

\s

空白文字にマッチ

スペース、タブ、改行になります。[\n\r\f\t]と同じです。

\S

空白文字以外にマッチ

[^\n\r\f\t]と同じです。

A|B|C

選択一致パターン(例では、A、B、Cのいずれか1つの文字にマッチ)

|の前後あるどちらかの正規表現にマッチします。

^,\A

行や文字列の先頭に一致する

場所に一致するので文字幅0となる。Perlでは通常^は行頭という意味ではなく、文字列の先頭を意味する(途中に改行文字を含んでいても)。複数行モードの場合に行頭の意味になる。\Aはモードの如何に関わらず、常に文字列の先頭を意味する。

$,\Z,\z

行や文字列の末尾に一致する

場所に一致するので文字幅0となる。Perlでは通常$は行末という意味ではなく、文字列の末尾を意味する(途中に改行文字を含んでいても)。複数行モードの場合に行末の意味になる。\Zはモードの如何に関わらず、行末を意味し、\zもモードの如何に関わらず、文字列の末尾を意味する。

\b

単語の区切りの位置にマッチする

これはPerlの場合、単語先頭が(?<!\w)(?=\w)、単語末尾では(?<=\w)(?!\w)を表している。

\G

もしくは最初は文字列の先頭、マッチング開始後はその直後の位置にマッチする

マッチングが繰り返されると、その度にその直後に変わる。

(...)

文字列をグループ化

カッコ内のパターンにマッチした文字列は記録されます。下記の$1などで参照できます。

(?:…)

後方参照を行わないグループ化

\1や\2を使用した後方参照が出来ません。この方がメモリーの節約になったり、スピードを速くする可能性があります。

(?=..),(?!..)

それぞれ、肯定先読み、否定先読み

表現は文字だが、場所に一致するので文字幅0となる。その文字が左にある場所に一致する。

(?<=..),(?<!..)

それぞれ、肯定後読み、否定後読み

表現は文字だが、場所に一致するので文字幅0となる。その文字が右にある場所に一致する。

\1,$1 グループ化にマッチした文字列を参照(使い方が微妙に違うので、注意を要します)
\

直後の文字をエスケープする

\を使って後続の文字をエスケープすると、メタ文字をリテラルとしてマッチさせることができます。

少しずつ上記を見ていきましょう。

任意の文字を表すドット(.)

正規表現の人気者「ドット(.)」です。これは通常は改行を除く、全ての文字にマッチします。通常というのはどういう意味があるかというと、何もオプションとして指定しない次のような場合は「通常」ということになります(オプションについては後述します)。

    $text =~ m{.*/};

オプションを何も指定していないので、この場合改行以外の文字が0個以上続き、その次に/があるものにマッチします。

次の例を考えてみると

$text = <<'EOF';
僕は、何もかも捨て去ってその時旅立つ決心をしていた。
そんな自分が好きであったし、それと同時に結果として逃
げだそうとしている自分を恥じていた。
EOF

if ( $text =~ m{そんな自分.+た。} ) {
    print "matched!\n";
}
else {
    print "no matched!\n";
}

が、もし「そんな自分」から文の最後までをマッチさせたいと思ったら、上記の正規表現m{そんな自分.+た。}ではうまくいきません。というのも、途中に改行があるので、.がその改行を含まないからです(ちなみに+は前の文字が1つ以上続くという意味で、この場合は「改行以外の文字が1つ以上続く」ということ)。

じゃあどうすれば良いかというと、あとにも出てくるオプションsを付けるとマッチします。つまり

$text = <<'EOF';
僕は、何もかも捨て去ってその時旅立つ決心をしていた。
そんな自分が好きであったし、それと同時に結果として逃
げだそうとしている自分を恥じていた。
EOF

if ( $text =~ m{そんな自分.+た。}s ) {
              ####################
    print "matched!\n";
}
else {
    print "no matched!\n";
}

[]内の文字のいずれかにマッチ(文字クラス)

[・・・]内の文字のいずれかにマッチさせたいときに用います。例えば、

$text = <<'EOF';
僕は、何もかも捨て去ってその時旅立つ決心をしていた。
そんな自分が好きであったし、それと同時に結果として逃
げだそうとしている自分を恥じていた。
EOF

$num = 1;
while ( $text =~ m{自分[がを]}sg ) {
    print "matched!/$num\n";
    $num++;
}

を実行すると

matched!/1
matched!/2

と2回matched!が表示されます。これは「自分」と「自分」がマッチしたからです。また、上記のようにgオプションを付けると、マッチが続くまで真を返すので、while文が回ります。

この表現には範囲指定も出来ます。つまり、

$text =~ m{[abcdefghijklmnopqrstuvwxyz]};

という表現は面倒なので

$text =~ m{[a-z]};

と表現できます。よく使われるのは

$text =~ m{[a-zA-Z0-9]};

などがあります。

また、^を開き大括弧のすぐ隣に配置すると、それ以外という意味になります。つまり、

$text =~ m{[^,]};

ですと、コンマ以外の文字にマッチします。この^はあくまでも開き大括弧のすぐ右隣にある場合にこの意味になるわけなので、そうでない場合は普通の文字としての^の意味になります(よくリテラルと解釈などと表現されます)。反対に範囲を表す-は開き大括弧の右隣、閉じ大括弧の左隣の場合は、ただのハイフンの意味になります。

$text =~ m{[-a-z^_]};

ですと、ハイフンか、小文字のアルファベットか、^か、_かのいずれかにとマッチさせる、という意味になります。

?,??

これは単独で使われるものではありません。その直前の文字が0個か1個の存在を許すということになります。つまり、存在しなくてもOKだということに注意が要します。??は非欲張りなマッチングを試みます。

$text = 'Hi, his hs is good.';
@match = $text =~ m{hi?s}g;

でマッチするのは、hishsですが、これは

$text = 'Hi, his hs is good.';
@match = $text =~ m{hi??s}g;

でも同じです。しかし、同じ結果に至る筋道が違います。前者は

Hi, his hs is good.
    ^
でhにマッチし、

Hi, his hs is good.
    ^^
でiにマッチし、

Hi, his hs is good.
    ^^^
でsにマッチします。次に、

Hi, his hs is good.
    ^^^ ^
hでマッチし、

Hi, his hs is good.
    ^^^ ^^
で、iとのマッチングに失敗します。なので、バックトラックして、

Hi, his hs is good.
    ^^^ ^^
iはなくてもOKなので、次のsとのマッチングをテストして、成功して終わります。

一方、??の場合は

Hi, his hs is good.
    ^
でhにマッチし、

Hi, his hs is good.
    ^^
でiのテストはとりあえず保留しておいて、iがsにマッチするかテストをしますが、失敗しバックトラックが起こります。

Hi, his hs is good.
    ^^
で、改めてiのテストが行われ、成功します。

Hi, his hs is good.
    ^^^
で次にsとマッチングが成功し、次に進みます。

Hi, his hs is good.
    ^^^ ^
で、hとのマッチングに成功し、

Hi, his hs is good.
    ^^^ ^^

無欲な??なのでiのテストは保留したまま、次のsをテストを行い、成功するので終了します。

これだとあまり違いがないようですが、

$text = '1234';
@match = $text =~ m{123?}g;

ですと123にマッチしますが、

$text = '1234';
@match = $text =~ m{123??}g;

ですと12にマッチします。明らかにマッチングが違ってきます。一方で、

$text = '1234';
@match = $text =~ m{123?4}g;

ですと1234にマッチしますが、

$text = '1234';
@match = $text =~ m{123??4}g;

1234にマッチします。つまり、??はその次にマッチングさせるような(アンカー的な)ものが存在する場合は、結局?と同じになってしまうので、もし、??の次にマッチングさせたいものがなく、さらにできれば??の前の文字とはマッチングさせたくない場合に利用するということになるでしょうか。

+,+?,*,*?

基本的に+は直前の文字やメタ文字が1個以上あることにマッチし、*は同様に直前の文字やメタ文字が0個以上あることにマッチします。

しかし、これはそれぐらいの曖昧な認識だと失敗してしまうことがあります。これら+*は、欲張りな量指定子です。この欲張りという意味は可能な限りマッチを続けようと、欲張りに文字を飲み込んでいくイメージです。

一方で、*?+?非欲張りな量指定子です。つまり、可能な限りマッチングをしないように振る舞います。

この辺をより具体的に見ていきましょう。

「^.*([0-9]+)」という正規表現で「Copyright 2005」をマッチさせたら、$1(括弧でグルーピングしたところ)には何が入るでしょうか(つまり()内は何にマッチングするでしょうか?)。

まず

.*

によって「何でもOKな文字が0個以上」続くということですが、この*や+は前述したように貪欲な奴で、可能な限りマッチさせていきます(これは後から出てくる、非欲張りの場合と違い、テストするかスキップするか、という選択肢のうち、欲張り量指定子の場合は---つまり*は---テストを優先させ、スキップしない、ということになります)。

つまり、

Copyright 2005
^

がOK。

Copyright 2005
^^

がOKと続いていき、

Copyright 2005
^^^^^^^^^^^^^^

まで、マッチさせます。正規表現エンジンは、その後、([0-9]+)に気づきます。ここがポイントで、人間と違い、次のマッチングのことは頭にないようです。つまり行くとこまでいって、その次を考えるということです。

「おや、次は数字が1個以上か」ということで、

Copyright 2005
^^^^^^^^^^^^^^

は、.*としてOKだとしても、次の([0-9]+)に合致しないので、マッチとしては失敗なので、1個、もどります(これはバックトラックという)。

Copyright 2005
^^^^^^^^^^^^^

そこで、「おお、これは([0-9]+としてOKだ」と、ここでマッチしたということで終了します。つまり、([0-9]+)には5だけがマッチするわけです。

この辺から若干自信はないのですが(説明が)、もし

^.*?([0-9])+だったら、どうなるかというと(つまり非欲張りの場合は)、

正規表現エンジンは、欲張りな*でなく、非欲張りな*?では、とりあえず自分自身のテストをしないで(スキップして)、次をテストします。

つまり

Copyright 2005
^

([0-9])+じゃない。そこで、失敗したので、前に戻って、さっきスキップした.*?をテストする。

Copyright 2005
^

でOK。

次に、たぶん、また.*?はスキップして、([0-9])+をテストする。

Copyright 2005
^^

でも失敗。そしてまたバックトラックして、.*?をテストして、

Copyright 2005
^^

はOK。そのまま失敗と成功を繰り返して、

Copyright 2005
^^^^^^^^^^

まで来るわけですが、今度も.*?をスキップして、([0-9])+をテストする。

Copyright 2005
^^^^^^^^^^^

でOKとなります。そこで.*?は終了し、([0-9])+のマッチングが始まり、

Copyright 2005
^^^^^^^^^^^^

でもOKと続いていき最後までたどり着いて、マッチング成功となるわけです。結局([0-9]+)は2005にマッチングします。

実際、以下のperlのスクリプトを実行すると

$test = "Copyright 2005";

if($test =~ /^.*([0-9]+)/){
    print $1."\n";
}

if($test =~ /^.*?([0-9]+)/){
    print $1."\n";
}

それぞれ

5
2005

と表示されます。後半の説明は若干自信がありませんが、一応予想通りの結果になります。

{min,max},{num},{num,}

これは+*とは違い、マッチングする文字数を指定する方法です。例えば、

$text = "123123312333123333123333333333";

while ( $text =~ /(3{3,})/g) {
    print $1,"\n";
}

をやった場合

333
3333
3333333333

が出力されますが、この$text =~ /(3{3,})/gは、3が3個以上続くものにマッチングさせています。これが

$text = "123123312333123333123333333333";

while ( $text =~ /(3{2,4})/g) {
    print $1,"\n";
}

ですと

33
333
3333
3333
3333
33

が出力され、この$text =~ /(3{2,4})/gは、3が2個以上、4個以下続くものにマッチングさせています。後半は

123123312333123333123333 3333 33
     ^^  ^^^  ^^^^  ^^^^ ^^^^ ^^

とマッチングしたため(わかるように途中にスペースを入れています)、3333が3つ出力されて33と3が2つ出力されているわけです。

さらに、これが

$text = "123|1233|12333|123333|123333333333";

while ( $text =~ /(\d{4,})/g) {
    print $1,"\n";
}

ですと

1233
12333
123333
123333333333

と出力され、これは「数字が4つ以上続く」ものにマッチしています。

\dは数字の意味、\Dは数字以外

文字クラスの[0-9]\dと表現することが可能です。また[^0-9]\Dを意味します。

例えば、文字列の中で、日付の形式を抜き出し、さらにそれらを年と月と日に分割するには

$text = "僕が生まれたのは1959-5-30だが、その3年前、つまり1956-05-24日に「売春防止法」が制定された。";

while ( $text =~ m{(\d{4})-(\d{1,2})-(\d{1,2})\D}g ) {
    print $1, "年", $2, "日", $3, "日\n";
}

を実行すると、

1959年5日30日
1956年05日24日

と出力されます。ただしこれですと9999-99-99もマッチしますので、もう少し厳密にする方法は後に触れます。

\n,\r,\f,\t

\nはMacを除いて(MacではCR)、WINDOWS、LINUX、DOSなどではLFを意味する。また、\rはMacを除いて(MacではLF)、WINDOWS、LINUX、DOSなどではCRを意味する。最近では、\fはほとんど使われませんが、改ページを意味しています。また、\tはタブを意味します。例えば、エクセルのデータをコピーアンドペーストをエディターにコピーすると、1つ1つのフィールドの分かれ目はタブになり、1行のレコードはCRLFで分かれています(数字にはダブルクォーテーションマークがつかず、文字データにはダブルクォーテーションマークがつきます。さらに、セル内改行なども出来ますが、ここでは無視しましょう)。次のようなデータを想定します。

1 タブ  2  タブ  "sakai"CRLF
3 タブ  4  タブ  "kazuro said ""booo!"""CRLF

1つのフィールドの中にダブルクォーテーションマークを入れる場合は2つ書きますので、最後の"kazuro said ""booo!"""はエクセル上ではkazuro said "booo!"と見ているものです。さてこれを解析するには、次のようなプログラムを走らせます。

$text = qq{1\t2\t"sakai"\r\n3\t4\t"kazuro said ""booo!"""};

while ( $text =~ m{(.+)(\r\n|$)}g ) {
    $line = $1;
    while ( $line =~ m{(?:^|\t)(?:"((?:[^\t]|"")*)"|([^\t])*)}g ) {
        if ( defined $1 ) {
            $field = $1;
            $field =~ s/""/"/g;
            print "[$field]";
            #"
        }
        else {
            print "[$2]";
        }
    }
    print "\n";
}

[1][2][sakai]
[3][4][kazuro said "booo!"]

が、出力されます。ちょっと複雑なので、解析すると、外側のwhile文の正規表現の$text =~ m{(.+)(\r\n|$)}gは、「CRLFか文字列末尾(最後の行に改行文字がない場合を想定して)で終わる、0文字以上の任意の文字列を取り出している部分です。要するに、1行が取り出せます。

次に1行を取り出して、次のwhile文では$line =~ m{(?:^|\t)(?:"((?:[^\t]|"")*)"|([^\t])*)}g という正規表現を使っていますが、これだとわかりづらいので、xオプションを使って、読みやすくすると

        $line =~ m{
                       (?:^|\t)            #文字列先頭か、タブで始まる
                       (?:                 #文字をまとめる(がキャプチャしない)
                           "               #ダブルクォーテーションがあって
                           ((?:[^\t]|"")*) #タブ以外かダブルクォーテーションが2つ続く
                           "               #ダブルクォーテーションで閉じる
                           |               #上記か、それとも
                           ([^\t])*        #ダブルクォーテーション以外が続く
                       )
                    }gx

と書けます。つまり、2つのケース、 [<ol></ol> is illegal in <p>] を、マッチングさせているわけです。

\wは文字と数字とアンダーバー、\Wはそれ以外

\w[a-zA-Z0-9_]を意味し、\Wはそれら以外を意味します。

\sは見えないスペース、タブ、改行文字、\Sはそれ以外

\s[\n\r\f\t]を意味します。したがって\S[^\n\r\f\t]です。

A|B|C

これはAかBかCにマッチするという意味になります。これだけですと文字クラスの[ABC]と同じ意味になりますし、文字クラスの方がパフォーマンスが良いらしいので、この程度の場合はあまり活躍が期待できません。

しかし、「SakaiかChikuの後ろの'とマッチさせたい」ような場合にはこの方法しかダメでしょう。

$text = << 'EOF';
Sakai's house was in Wakayama in '70s.
But Chiku's house was in Kanagawa in '80s.
EOF
while ( $text =~ m{((Sakai|Chiku)')}g ) {
    print "$1\n";
}

を実行すると、

Sakai'
Chiku'

と出力されます。

^,\A,$,\Z,\z

節s,mでも見てきたように、これは話が込み入ってきます。

  1. m修飾子を付けない場合。つまり複数行モードの場合。
    1. ^

      仮に途中に改行文字があったとしても、文字列の一番最初にマッチします。

    2. $

      仮に途中に改行文字があったとしても、文字列の一番最後にマッチします。

  2. m修飾子を付けた場合。つまり行モードの場合。
    1. ^

      仮に途中に改行文字がなかったら、文字列の先頭に、途中に改行文字があった場合は、その改行文字のすぐ前にもにマッチします。

    2. $

      仮に途中に改行文字がなかったら、文字列の最後に、途中に改行文字があった場合は、その改行文字のすぐ後にもにマッチします。

  3. モードに関係なし。
    1. \A

      仮に途中に改行文字があったとしても、文字列の一番最初にマッチします。

    2. \Z

      仮に途中に改行文字があったら、その改行文字のすぐ後にマッチします。

    3. \z(小文字)

      仮に途中に改行文字があったとしても、文字列の一番最後にマッチします。

\G

これも^,$,\A,\Z,\zなどと同様、場所にマッチします。ただし、徐々にその場所が変化していくことが特徴です。マッチングか開始する際の最初は、文字列の先頭にマッチしますが、マッチ後は、マッチした文字列とその次の文字列の間、つまりその位置にマッチするものです。

あまり良い例が浮かばないので、以前書いたものを引用しますが、半角文字のtrim(文字列の前後の空白を削除)は簡単ですが、どうやら全角スペースの変換の例です。以下はEUC_JPの場合の話です。

$str     = 'これはテストです';
$pattern = '好';

if ($str =~ /$pattern/) {
  print "match!\n";
}

は、何故かマッチしてしまいます。何故でしょうか?

それは『EUC-JP の「ス」の文字コードは 0xA5 0xB9 ,「ト」は 0xA5 0xC8,「好」は 0xB9 0xA5 であり,ちょうど「スト」の真ん中の部分が「好」と同じになるのでマッチしてしまうのです。

$str     = 'これはテストです';
$pattern = '好';
$ascii = '[\x00-\x7F]';                     #ASCII
$twoBytes = '[\x8E\xA1-\xFE][\xA1-\xFE]';   #半角カタカナと全角文字
$threeBytes = '\x8F[\xA1-\xFE][\xA1-\xFE]'; #補助漢字

if ($str =~ /^(?:$ascii|$twoBytes|$threeBytes)*?(?:$pattern)/) {
  print "match\n";
}

とするとマッチしません。どうしてかというと、EUC-JP での 1文字というのは 1バイト文字である ASCII, 2バイト文字である JIS X 0201片仮名(半角カタカナ)と JIS X 0208(全角文字),3バイト文字である JIS X 0212(補助漢字)のことなので、その3種類の文字が0以上続いた後のマッチさせたいパターンというように指定したからです。つまり、文字の途中でマッチしてしまうような場合を避けたわけです。

これを利用すると、全角文字のtrimは次のように書けます。

sub trim {
    my $text        = shift;
    my $ascii       = '[\x00-\x7F]';
    my $two_bytes   = '[\x8E\xA1-\xFE][\xA1-\xFE]';
    my $three_bytes = '\x8F[\xA1-\xFE][\xA1-\xFE]';
    $text =~ s/\G((?:$ascii|$two_bytes|$three_bytes)*?)(?:\xA1\xA1)/$1/g;
   return $text;
}

最後の

$text =~ s/\G((?:$ascii|$two_bytes|$three_bytes)*?)(?:\xA1\xA1)/$1/g;

を簡単に見ておくと、まずはわかりやすいようにベストプラクティスが推奨している方法に書き直して、

$text ="  サカイは馬鹿じゃありませ んよ  ";
$text =~ s{\G                                     #最初は行頭、マッチ後はマッチした最後
           (                                      #グルーピング開始
            (?:$ascii|$two_bytes|$three_bytes)*?  #アスキー・2バイト・3バイト文字が0以上
           )                                      #グルーピング終了
            (?:\xA1\xA1)}                         #全角スペース
          {$1}gmx;                                #上でマッチした文字列だけに全部置換

という感じでしょうか。順に追って考えると、

  1. \G は、マッチングが行われる前(つまり最初)は先頭に一致します。
  2. 上の例では、 $text の最初は全角スペースですが、「アスキー・2バイト・3バイト文字が0個以上で非欲張りマッチング」なので何もなくてもOKだから、次の正規表現((?:\xA1\xA1))で全角スペースで一致します。
  3. そして、 $1 にはグルーピングしたところにはマッチしていないので、何も入っていません。その全角は何もない $1 で置換されます(つまり消える)。
  4. \G はマッチした場合にはマッチした文字の次の位置を意味するので、 \G・ は先ほどの消されたスペースがあった次を意味します。今回だと再度全角スペースに一致し、3が起こります。
  5. 次は、途中に全角スペースなどもありますが、全角スペースなども全て「アスキー・2バイト・3バイト文字」に含まれるので、グルーピング部分は最後の全角スペースの直前までマッチし、さらに次の全角スペースでマッチ全体が終わります。そして、その「アスキー・2バイト・3バイト文字」+「全角スペース」が「アスキー・2バイト・3バイト文字」で置き換わるわけです。
  6. まだ最後の全角スペースが残っています。5の後、最後の全角スペースの直前に \G の位置がありますので、3と同様に全角スペースは消えてしまいます。

とうわけで、\Gは動的にマッチした場所を覚えておいて、その場所にマッチさせるような場面で有効です。

(..),(:..),\1,$1

もう既に、このグルーピングは多用してきましたが、小括弧でくくるとそのくくられたところをグルーピングします。例えば、

$text =~ m{(Sakai|Chiku)};

のように、「SakaiかChiku」にマッチであったり、

$text = << 'EOF';
Sakai's house was in Wakayama in '70s.
But Chiku's house was in Kanagawa in '80s.
And Sakai has gone to Africa!
Sakai may be somewhere?
EOF

while ( $text =~ m{(Sakai.+(\.|!|\?))+}g) {
    print "$1\n";
}

「Sakaiで始まる文」にマッチさせるようなときに使います。

ただし、このグルーピングには副作用があります。それは、この小括弧を利用すると、Perlは自動的に\1,$1などの変数に値を格納します。

この\1,$12つの使い方の違いは、正規表現内でマッチした値を利用する場合は\1を、正規表現を抜けた後で、利用したい場合は$1を利用します。例えば、\1

$text = << 'EOF';
Sakai's house was in Wakayama in '70s.
But Chiku's house was in in Kanagawa in '80s.
And Sakai has gone to Africa!
Sakai may be be somewhere?
EOF

while ( $text =~ m{\s(\w+)\s\1}g) {
    print "$1\n";
}

のように、同じ単語が続いてしまうものを検知する正規表現で、上記は「スペースがあって、その次に英数字が続き(ここでグルーピングしている)、さらにスペースが続いて、次に先ほどマッチした英数字と同じものが出現する」というその「同じもの」を\1で参照しています。或いは

$text = << 'EOF';
Sakai's house was in Wakayama in '70s.
But Chiku's house was in in Kanagawa in '80s.
And Sakai has gone to Africa!
Sakai may be be somewhere?
EOF

$text =~ s{\s(\w+)\s\1 }{ \1 }g;
print "$text\n";

のように、一気に同じ単語が続くものを1つに置換してしまうことも可能です。一方、$1の方は、正規表現を評価する(マッチしたり、置換した)後に、利用する場合に使います。例えば(今までさんざん出てきましたが)、

\でエスケープ

上記で見てきたように、\は特別な意味を持っています。

実践例

Hidemaruでの置換

秀丸の正規表現はちょっと癖があるので、あまり良い例にはならないかもしれませんが、逆に知っていると便利という点において、Perlの例よりも良いかと思い、少し例を出してみましょう。

置換1

例えば以下のような、スクリプトがあったとします。

__PACKAGE__->add_columns(qw/id kana old_name name sex birthday zip1 zip2 \
    cellphone uid model model_number carrier image1 image2 mail_address \
    background background_other delivery_offer delivery_confirm status \
    date_counter status4_check_flag status6_check_flag userid_check_flag \
    retry_count create_user_id created_date update_user_id updated_date \
    reserve1 reserve2 reserve3/);

これを

my $id = $instance->id;
my $kana = $instance->kana;
...

を作りたいとします。もちろん、手作業で作成することは可能ですが、秀丸の置換を使って

検索文字:(qw/| +)\f[^ /]+\f
置換文字:\n$\1 = $instance->\1;

として、全置換を一気に行うと

__PACKAGE__->add_columns(
$id = $instance->id;
$kana = $instance->kana;
$old_name = $instance->old_name;
$name = $instance->name;
$sex = $instance->sex;
$birthday = $instance->birthday;
$zip1 = $instance->zip1;
$zip2 = $instance->zip2;
$cellphone = $instance->cellphone;
$uid = $instance->uid;
$model = $instance->model;
$model_number = $instance->model_number;
$carrier = $instance->carrier;
$image1 = $instance->image1;
$image2 = $instance->image2;
$mail_address = $instance->mail_address;
$background = $instance->background;
$background_other = $instance->background_other;
$delivery_offer = $instance->delivery_offer;
$delivery_confirm = $instance->delivery_confirm;
$status = $instance->status;
$date_counter = $instance->date_counter;
$status4_check_flag = $instance->status4_check_flag;
$status6_check_flag = $instance->status6_check_flag;
$userid_check_flag = $instance->userid_check_flag;
$retry_count = $instance->retry_count;
$create_user_id = $instance->create_user_id;
$created_date = $instance->created_date;
$update_user_id = $instance->update_user_id;
$updated_date = $instance->updated_date;
$reserve1 = $instance->reserve1;
$reserve2 = $instance->reserve2;
$reserve3 = $instance->reserve3;/);