正規表現

■ 文字列を正規表現で調べる(/.../)

◆ 正規表現とは

「Aで始まる文字列」や、「ABCを含む文字列」など、文字列が指定したパターンにマッチしているかどうかを調べることができます。このパターンを 正規表現(→「正規表現一覧」)と呼びます。例えば、文字列が「ABCという文字列を含んでいるかどうか」を調べるには次のようにします。

regexp1.pl
# ABC を含んでいるか調べる
$str = "ABCDEFG";
if ($str =~ /ABC/) {
    print "ABCを含んでいます。\n";
}
◆ =~ 演算子と !~ 演算子

演算子 =~ はマッチする場合に真、!~ はマッチしない場合に真となります。

regexp2.pl
# ABC を含んでいないか調べる
$str = "XYZXYZ";
if ($str !~ /ABC/) {
    print "ABCを含んでいません。\n";
}
◆ 省略時変数 $_ のマッチング

/.../ のみを記述した場合は、省略時変数 $_ がマッチングの対象となります。

regexp3.pl
open(IN, "data.txt");
while (<IN>) {            # while ($_ = <IN>) { と同義
    if (/From:/) {        #    if ($_ =~ /From:/) { と同義
        print;
    }
}
close(IN);
◆ メタ文字

正規表現では、^ $ ? + * { } [ ] ( ) . \ などの記号は特別な意味を持ちます。例えば + は「1文字以上の」を意味します。これらの特別な意味を持つ記号を メタ文字 と呼びます。メタ文字の意味を打ち消すにはバックスラッシュ(\)でメタ文字をエスケープします。

if ($str =~ /A+/) { print "1文字以上のAを含みます。\n"; }
if ($str =~ /A\+/) { print "A+を含みます。\n"; }

■ ~で始まる、~で終わる(^, $)

◆ ~で始まる、~で終わる

ハット(^)は「~で始まる」、ドル記号($)は「~で終わる」を意味します。^ や $ を用いない場合は、「~を含む」という意味になります。

regexp4.pl
$str= "ABCXYZ";
if ($str =~ /^ABC/) { print "ABCで始まっています。\n"; }
if ($str =~ /XYZ$/) { print "XYZで終わっています。\n"; }
if ($str =~ /^ABCXYZ$/) { print "ABCXYZです。\n"; }
if ($str =~ /ABCXYZ/) { print "ABCXYZを含んでいます。\n"; }

上記を実行すると、結果は次のようになります。

ABCで始まっています。
XYZで終わっています。
ABCXYZです。
ABCXYZを含んでいます。

■ ~または~((...|...))

◆ ~または~

(...|...) は、「... または ...」を意味します。

$str = "ABCXYZ";
if ($str =~ /(ABC|XYZ)/) { print "ABCまたはXYZを含みます。\n"; }
if ($str =~ /^(ABC|XYZ)$/) { print "ABCまたはXYZです。\n"; }

(...|...|...) のようにいくつでも使用することができます。

if ($str =~ /(ABC|HIJ|XYZ)/) {
    print "ABCまたはHIJまたはXYZを含みます。\n";
}

■ n文字の~(?, *, +, {n,m})

◆ 文字数の指定

例えば「3文字のAAA」は以下のように表現することができます。

if ($str =~ /^AAA$/) { print "3文字のAです。\n"; }

もう少し数が増えたり、もう少し複雑な個数指定を行いたい場合、次のような表現を用いることができます。

◆ 0~1文字の

クエスチョン(?)は「0文字か1文字の」、アスタリスク(*)は「0文字以上の」、プラス(+)は「1文字以上の」を意味します。いずれも直前の 1文字に対して意味を持ちます。

if ($str =~ /^A?$/) { print "0文字か1文字のAです。\n"; }
if ($str =~ /^A*$/) { print "0文字以上のAです。\n"; }
if ($str =~ /^A+$/) { print "1文字以上のAです。\n"; }
◆ n~m文字

{n} は「n文字の」、{n,} は「n文字以上の」、{n,m} は「n文字以上m文字以下の」を意味します。これも直前の 1文字に対して意味を持ちます。

if ($str =~ /^A{3}$/) { print "3文字のAです。\n"; }
if ($str =~ /^A{3,}$/) { print "3文字以上のAです。\n"; }
if ($str =~ /^A{3,5}$/) { print "3文字以上5文字以下のAです。\n"; }
◆ (...)との組み合わせ

直前の文字ではなく、直前の文字列の個数指定を行いたい場合、文字列を (...) で囲みます。例えば (ABC){3} とすると「ABCが3回」、つまり "ABCABCABC" となります。

if ($str =~ /^(ABC)?$/) { print "ABCか空文字です。\n"; }
if ($str =~ /^(ABC)*$/) { print "空文字か、ABCか、ABCABCか...\n"; }
if ($str =~ /^(ABC)+$/) { print "ABCか、ABCABCか、ABCABCABCか...\n"; }
if ($str =~ /^(ABC){3}$/) { print "3回のABCです。\n"; }
if ($str =~ /^(ABC){3,}$/) { print "3回以上のABCです。\n"; }
if ($str =~ /^(ABC){3,5}$/) { print "3回以上5回以下のABCです。\n"; }

■ ~の中のいずれか1文字([...])

◆ いずれか1文字

[...] は、その中の文字列のうち、いずれか1文字にマッチします。例えば、[ABC] は A または B または C のいずれかを含んでいることを示します。

if ($str =~ /[ABC]/) { print "ABC のいずれかを含んでいます。\n"; }
◆ ○~○の中のいずれか1文字

ハイフン(-)を用いて範囲指定することも可能です。[A-Z] は、A~Z のいずれかを意味します。両方の形式を組み合わせることも可能です。

if ($str =~ /[A-G]/) { print "A~G のいずれかを含んでいます。\n"; }
if ($str =~ /[0-9A-F]/) { print "0~9、A~F のいずれか・・・\n"; }
if ($str =~ /[ABC0-9]/) { print "A~C、0~9 いずれか・・・\n"; }

ハイフン(-)が [ の直後にある場合は、通常のハイフン記号として扱われます。

if ($str =~ /[-ABC]/) { print "ハイフン、A、B、Cのいずれか・・・\n"; }

■ ~以外の文字([^...])

◆ ~以外の文字

[^ABC] は、ABC 以外の文字にマッチします。行頭を示すハット(^)と同じ記号を用いますが、まったくの別物です。下記の例では、0~9、a~z、A~Z 以外の文字にマッチします。

if ($str =~ /[^0-9a-zA-Z]/) {
    print "0~9、a~z、A~Z 以外の文字を含んでいます。\n";
}

[ABC^DEF] のようにハット(^)の場所が [ の直後でない場合は、通常のハット記号として扱われます。

if ($str =~ /[_^]/) {
    print "アンダーバー(_)もしくはハット(^)を含んでいます。\n";
}

■ 特殊文字(\r, \n, \t, \f, \a, \e, ...)

◆ 特殊文字

文字列の中と同様、正規表現の中でも 復帰(\r)、改行(\n)、タブ(\t)、フォームフィード(\f)、アラーム(\a)、エスケープ(\e)、コントロール(\c[)などのエスケープシーケンスを使用することができます。

if ($str =~ /[\r\n]/) {
    print "改行を含んでいます。\n";
}

例えば、JISコードの漢字INコード(ESC $ @ または ESC $ B)は、下記のように表現します。

if ($str =~ /(\e\$@|\e\$B)/) {
    print "JISコードです。\n";
}

特殊文字の一覧は「エスケープシーケンス一覧」を参照してください。

■ 任意の~文字(\d, \D, \w, \W, \s, \S)

◆ 任意の~文字

ドット(.)は改行(\n)を除く任意の1文字 [^\n]、\d は任意の半角数字 [0-9]、\w はアンダーバーを含む半角英数字 [_0-9a-zA-Z]、\s はスペース文字 [ \t\n\r\f]、\D は \d 以外の文字 [^0-9]、\W は \w 以外の文字 [^_0-9a-zA-Z]、\S は \s 以外の文字 [^ \t\r\n\f] を意味します。

if ($str =~ /./) { print "任意の文字を含んでいます。\n"; }
if ($str =~ /^\d+$/) { print "1文字以上の数字です。\n"; }
if ($str =~ /^\w+$/) { print "1文字以上の半角英数字です。\n"; }
if ($str =~ /^\s+$/) { print "1文字以上のスペース文字です。\n"; }

「ABCなんとかXYZ」をよく ABC.*XYZ と表現しますが、. は改行(\n)を含まない点に注意してください。例えば HTML のコメントを削除する際に .* を用いると改行を含むコメントの削除に失敗します。この場合は (.|\n) を用いてください。

$html =~ s/<!--.*?-->//g;           # 改行を含むコメントを削除できない
$html =~ s/<!--(.|\n)*?-->//g;      # 改行を含んでいても大丈夫

■ 大文字・小文字を無視してマッチングする(/.../i)

◆ 大文字・小文字を区別しない

/.../i は、アルファベットの大文字と小文字を区別しないで比較します。

regexpi.pl
$str = "This is Japan";
if ($str =~ /THIS IS JAPAN/) {          # 大文字・小文字が区別される
    print "マッチしました(1)。\n";
}
if ($str =~ /THIS IS JAPAN/i) {         # 大文字・小文字が区別されない
    print "マッチしました(2)。\n";
}

実行結果は次のようになります。

マッチしました(2)。

/.../gio のように、他の正規表現オプションと組み合わせて使用することができます。

■ 連続してマッチさせる(/.../g)

◆ 連続マッチング

/.../g は、文字列に対してマッチングを連続的に行います。下記の例では、文字列 $str の中に is が含まれている回数だけ(例では2回)「マッチしました。」が表示されます。

regexpg.pl
$str = "This is Japan.";
while ($str =~ /is/g) {
    print "マッチしました。\n";
}

配列のコンテキストで使用された場合は、マッチした部分の配列を返します。

$str = "1999/12/31";
@yymmdd = ($str =~ /\d+/g);
print "$yymmdd[0]年 $yymmdd[1]月 $yymmdd[2]日\n";

■ 高速にマッチさせる(1)(/.../o)

◆ o オプションによる高速マッチング

/.../o は、マッチングを高速に行います。通常は、マッチングを行うたびに、正規表現のコンパイル作業が発生しますが、o オプションを指定することにより、コンパイルを最初の1回のみに限定することにより実現されます。ただし、指定する正規表現を、変数を用いて動的に変更することはできません。

regexpo.pl
open(IN, "data.txt");
while (<IN>) {
    if (/^Subject:/o) {
        print;
    }
}
close(IN);

■ 高速にマッチさせる(2)(study)

◆ study による高速マッチング

study() は、マッチングを行う対象の文字列をあからじめ評価することによりマッチングを高速化します。この高速化は、o よりも f の方が出現頻度が少ないといった、英文の特徴を元に行われています。文章によっては逆に遅くなることもあります。マッチングの対象が英文以外の場合は、あまり期待できないかもしれません。

while ($line = <>) {
    study($line);
    if ($line =~ /this/) { ... }
    if ($line =~ /is/) { ... }
    if ($line =~ /Japan/) { ... }
       :
}

■ マッチした文字列を取り出す($1, $2, $3, ...)

◆ n番目にマッチした部分の文字列

特殊変数 $1, $2, ... は、直前に実行した正規表現マッチングの、最初の (...)、2番目の (...)、3番目の (...) にそれぞれ対応する部分の文字を示します。

regexp5.pl
$str = "12:59:59";
if ($str =~ /^(\d\d):(\d\d):(\d\d)$/) {
    print "[$1] [$2] [$3]\n";
}

結果は次のようになります。

[12] [59] [59]

括弧が入れ子になっている場合は、左側の括弧 ( の順番で参照します。下記の例で BBB に相当する変数は $2 になります。

if ($str =~ /(AAA(BBB)*CCC|XXX(YYY)*ZZZ)/) { ... }

■ マッチした文字列を取り出す($`, $&, $')

◆ マッチした部分、その前後の部分の文字列

$` はマッチした部分の左側の文字列、$& はマッチした部分の文字列、$` はマッチした部分の右側の文字列を示します。

regexp6.pl
$str = "123456789";
if ($str =~ /456/) {
    print "[$`] [$&] [$']\n";
}

結果は次のようになります。

[123] [456] [789]

■ スラッシュを多く含むマッチングを行う(m/.../)

◆ 「斜めつまようじ症候群」の回避

スラッシュ(/)を多く含む文字列をマッチングさせる場合、どうしてもバックスラッシュ(\)を多用しがちになります。これは、「斜めつまようじ症候群」と呼ばれています。英語フォントの \ は \ と表示されるので想像はできますね。m/.../ は /.../ と同じ意味を持ちますが、m|...| や m(...) など、スラッシュの代わりに任意の記号や括弧を用いることができます。これにより、「斜めつまようじ症候群」を回避することができます。

# 斜めつまようじ症候群の例
if ($str =~ /\/usr\/local\/bin\/perl/) {
    print "マッチしました。\n";
}

# ちょっとエレガントな例
if ($str =~ m(/usr/local/bin/perl)) {
    print "マッチしました。\n";
}

■ 最初に現れるパターンを捜す(?...?)

◆ 最初に現れるパターンを捜す

?...? は /.../ と同様の動作をしますが、一度マッチしてしまうと reset() が行われるまで二度とマッチしません。reset() は、?...? のフラグをリセットして、再度マッチできるようにします。例では、デリミタで区切られた各メールの From: ヘッダを表示しますが、本文にたまたま現れる From: という文字列にマッチしないようにしています。

open(IN, "mail.txt");
while (<IN>) {
    if (?^From: (.*)?) {
        print "From=$1\n";
    }
    if (/^----delimiter----$/) {
        reset();
    }
}
close(IN);

■ 直前のマッチングの位置を得る(pos)

◆ 直前のマッチングの位置を得る

pos() は、直前に行われた /.../g マッチングの位置を、1文字目を0と数えたオフセット値で返します。

$xx = "This is Japan.";
while ($xx =~ /i/g) {
    print pos($xx) . "\n";
}

上記の例では、文字 "i" が 3番目と 6番目でマッチするため、実行結果は下記のようになります。

3
6

■ メタ文字を無効化する(quotemeta)

◆ メタ文字の無効化

quotemeta() は、文字列中に含まれる記号文字([_A-Za-z0-9] 以外の文字)の前にバックスラッシュ(\)を付加したものを返します。これは、文字列中の記号が正規表現のメタ文字として扱われてしまうのを防ぎます。

$str1 = "1*2+3=5";
$str2 = quotemeta($str1);
print "$str2\n";

結果は次のようになります。

1\*2\+3\=5

■ 数値の正規表現

◆ 数値の正規表現

正規表現の実用的サンプルとして、数値の正規表現を示します。マイナス(-)もしくはプラス(+)が 0文字か 1文字あり、その後ろに数字が 1文字以上続きます。

$str =~ /^[-+]?\d+$/;

小数を含む場合は次のようになります。

$str =~ /^[-+]?\d+(\.\d+)?$/;

指数部をを含む場合は次のようになります。

$str =~ /^[-+]?\d+(\.\d+)?([eE][-+]?\d+)?$/;

■ 時刻表記の正規表現

◆ 時刻の正規表現

「時:分:秒」にマッチする正規表現は下記の通りです。時や分や秒は2桁で記述されているものとします。

$str = "23:59:59";
if ($str =~ /^\d\d:\d\d:\d\d$/) {
    print "マッチしました。\n";
}

1桁を許す場合は、\d\d を \d{1,2} に変更してください。

$str = "8:30:00";
if ($str =~ /^\d{1,2}:\d{1,2}:\d{1,2}$/) {
    print "マッチしました。\n";
}

■ メールアドレスの正規表現

◆ メールアドレスの正規表現

フォームなどに入力されたメールアドレスが正しそうなものか判定します。

$email = 'foo@xxx.yyy.zzz';

if ($email !~ /^[-\w\.]+@[-\w\.]+$/) {
    print "メールアドレスが不正です。\n";
}

ただし、上記の正規表現は正確なものではありません。一般的に使用されているメールアドレスの形式にマッチしているかの簡易チェックになります。下記のようにすると少しだけ正確になりますが、本当に正確な正規表現を書こうとするととんでもなく長いものになるそうです。

if ($email !~ /^[\w\Q!#$%&'*+-\/=?^_`{|}~\E]+@([-\w]+\.)*[-\w]+$/) {
    print "メールアドレスが不正です。\n";
}

■ URLの正規表現

◆ URLの正規表現

ホームページのアドレス(URL)の正規表現は次のようになります。

$url = "http://www.yyy.zzz:8080/abc/index.htm";
if ($url =~ /^(http|https):\/\/[-\w\.]+(:\d+)?(\/[^\s]*)?$/) {
    print "マッチしました。\n";
}

正規表現を用いて、URLのプロトコル部、ホスト名、ポート番号、パス名を取得するには次のようにします。

$url = "http://www.yyy.zzz:8080/abc/index.htm";
$url =~ /^(http|https):\/\/([-\w\.]+)(:(\d+))?(\/[^\s]*)?$/;
print "prot=[$1] host=[$2] port=[$4] path=[$5]\n";

実行結果は次のようになります。

prot=[http] host=[www.yyy.zzz] port=[8080] path=[/abc/index.htm]

Copyright (C) 2002 杜甫々