手を動かしながら覚える正規表現<基礎入門編>

スポンサーリンク

はじめに

正規表現というと、便利そうなんだけど読み方がわからないし、わけのわからない記号が並んでいてあまり近づきたくないという人もいると思います。ネット上のサンプルを見ると記号がたくさん並んでいて取っつきにくそうな雰囲気があって、中には魔法の呪文だろうこれはと言いたくなるようなものもあります。しかし、普段使うような正規表現は少しのルールを覚えるだけでたいてい読めるようになります。簡単なものであれば、慣れてしまえば一瞬で読めるようになりますし、慣れないうちでもルールに沿って読んでいけば何が書いてあるのかは意外と簡単に読めるものなのです。

このページでは、基本的な正規表現を紹介しています。説明を読むだけではなかなか覚えられないという方のために、実際に正規表現を入力して動作を確認できる練習問題も用意しています。

正規表現は一度しっかり覚えてしまえば二度と忘れることはありませんので、ぜひここで確実に身につけていって下さい。

正規表現を読むぐらいなら大丈夫という方には置換処理編を用意しています。

登場した正規表現はリファレンスにまとめてありますので、まとめて振り返る時などはこちらをご覧下さい。

正規表現とは

正規表現というのは、文字列の集まりを表現する方法です。以下、具体的にどのようにして文字列の集まりを表現するのかを説明しますが、その前に、正規表現以外の文字列の集まりを表現する方法を紹介します。

一つ目は、Windowsのコマンドプロンプトのワイルドカードです。例えばコマンドプロンプトで拡張子「.txt」のファイル名一覧を表示する時には「dir *.txt」と入力します。そうすると以下のように拡張子が「.txt」のファイルだけが表示されます。

> dir *.txt

2008/10/01  01:23             1,670 説明書.txt
2008/10/01  01:23             9,512 readme.txt

「*.txt」のアスタリスク(*)が「0文字以上の文字列」という文字列の集まりを表現しているので、上記例の「説明書」や「readme」が*の部分にマッチして結果的に拡張子「.txt」の一覧を表示することができるのです。

二つ目は、SQLのLIKEです。例えばSQLのSELECTで「株式会社」で始まるレコードを検索する場合WHERE句にLIKE '株式会社%'と入力します。そうすると以下のように「株式会社」で始まるレコードだけが表示されます。

SQL> SELECT name
FROM companies
WHERE name LIKE '株式会社%'
;

name
----------------------------------------
株式会社○○○組
株式会社○○製作所
株式会社×××

「株式会社%」の%が「0文字以上の文字列」という文字列の集まりを表現しているので、上記例の「○○○組」や「○○製作所」が%の部分にマッチして結果的に「株式会社」で始まるレコードを検索することができるのです。

例を二つ紹介しました。これで「文字列の集まり」というのがどんなことを言っているのか分かったでしょうか。それでは次からは正規表現の具体的な例を見ていきます。

ある文字にマッチ

まずは1文字の正規表現を紹介しましょう。「a」です。アルファベットのaが1文字だけ。これも正規表現です。簡単ですね。

ここで、いきなりですが問題です。正規表現「a」は次のうちどの文字列にマッチするでしょうか。

A
B
a
b
lemon
orange

「A」と「a」と答えた方は残念。「a」と「orange」が正解です。コマンドプロンプトの場合「A」と「a」が正解になりますが、正規表現では間違いです。

正規表現ではアルファベットの大文字と小文字を区別します。ですので正規表現「a」は文字列「A」にマッチしません。

また、正規表現の場合文字列の一部となっていればマッチします。文字列「orange」の3文字目に「a」があるので、正規表現「a」は文字列「orange」にマッチします。

[ 補足 ] 厳密には正規表現「a」は文字列「orange」の「a」の部分にマッチするというのが正しいです。ここでは「正規表現が文字列とマッチする」という言葉を「正規表現が文字列の一部とマッチする」という意味で使っています。

それでは早速練習してみましょう。「入力」の各文字列に対して適用する正規表現を考えて下さい。回答を入力して「確認」ボタンをクリックすると、入力した正規表現にマッチする文字列を下の欄に表示します。これが「出力」の文字列を一致すると正解です。

文字列中にアルファベット小文字のエル(l)を含むものにマッチする正規表現を入力して下さい。

入力
出力
回答入力:
l

文字列にマッチ

文字列というのは文字が複数連続したものを指します。例えば「an」。それでは、正規表現「an」は次のうちどの文字列にマッチするでしょうか。

lemon
orange
apple
watermelon
banana

「orange」と「banana」が正解です。「watermelon」は「a」と「n」が含まれていますが、連続していないのでマッチしません。「orange」や「banana」のようにそのままの形で一致するところにマッチします。

それでは練習しましょう。

文字列中に「you」を含むものにマッチする正規表現を入力して下さい。

入力
出力
回答入力:
you

行頭にマッチ

ある文字やある文字列にマッチする正規表現の書き方を覚えました。しかし、これだけでは普通の文字列検索と変わりがありません。これまではほんの小手調べで、ここからが本番です。正規表現らしく記号が登場します。

まずは行頭にマッチする正規表現です。例えば次のPerlのスクリプトからコメント行(#で始まる行)にマッチする正規表現を書いてみたいと思います。

# 円の面積を求める
use strict;
use warnings;

my $pi = 3.14; # 円周率
my $r = 10; # 半径

# 円の面積=円周率×半径×半径
print $pi * $r * $r, "\n";

単純に「#」としてしまうと次のように行頭以外の「#」にもマッチしてしまって、上手く行きません。

# 円の面積を求める
use strict;
use warnings;

my $pi = 3.14; # 円周率
my $r = 10; # 半径

# 円の面積=円周率×半径×半径
print $pi * $r * $r, "\n";

このような時は行頭を表す「^」と組み合わせて「^#」とします。こうすると次のように行頭の直後にある「#」にだけマッチします。

# 円の面積を求める
use strict;
use warnings;

my $pi = 3.14; # 円周率
my $r = 10; # 半径

# 円の面積=円周率×半径×半径
print $pi * $r * $r, "\n";

^」は特定の文字にマッチするわけではないのでちょっと違和感があるかもしれません。でもそういう決まりですので慣れてしまいましょう。

行頭は「^」。覚えましたね。それでは練習しましょう。

JavaScriptのコメント行(行頭が//)にマッチする正規表現を入力して下さい。

入力
出力
回答入力:
^//

行末にマッチ

行頭の次は行末です。行頭は「^」でしたね。行末は「$」です。例えば「株式会社」で終わる文字列にマッチする正規表現は「株式会社$」になります。これで「株式会社」の直後が行末の文字列にマッチします。

日本○○○株式会社
株式会社○○組
株式会社○○製作所
○○商事株式会社

以上で説明終わりです。練習に行きましょう。

「太」で終わる文字列にマッチする正規表現を入力して下さい。

入力
出力
回答入力:
太$

どれかにマッチ(選択)

ファイル名の一覧からGIFファイルとPNGファイルのファイル名にマッチさせようとすると、拡張子「.gif」と「.png」の二つにマッチさせたくなりますね。こんな時は選択を使います。選択を正規表現で書く時は縦棒(|)を使います。

gif」または「png」にマッチさせる正規表現は「gif|png」となります。マッチさせるパターンを縦棒で区切ります。

ただし、単に「gif|png」だと「gift.doc」なんていうのにもマッチしてしまうので今回の場合は「\.(gif|png)$」とします。これで「ピリオドのあとに『gif』または『png』があり、そのあとに行末がある」文字列にマッチします。

選択の対象をはっきりさせるために括弧で囲むのを忘れないようにして下さい。

arrow.gif
fall.jpg
gift.doc
snow.png
tgif

正規表現が「.(gif|png)$」ではなくて「\.(gif|png)$」となっています。ピリオドには特別な意味があって、単にピリオドを書くと別の意味になってしまうので「\」(円マークないしはバックスラッシュ)を使ってエスケープしているのです。ピリオドのような特別な機能を持つ文字をメタ文字と呼んでいます。

これまでにも既にメタ文字が出てきています。「^」と「$」ですね。メタ文字に使われている文字を検索したい場合には「\」を前に付けてエスケープするという決まりになっていますので注意して下さい。

ピリオドがどんな意味を持つのかはあとで説明しますのでお楽しみに。

どれかにマッチさせる場合は縦棒を使うというのは覚えましたね。それでは練習しましょう。

「合同会社」または「株式会社」で始まる文字列にマッチする正規表現を入力して下さい。

入力
出力
回答入力:
^(合同|株式)会社

大文字小文字の同一視

最初の方で正規表現ではアルファベットの大文字と小文字を区別しますと説明しました。そのために「\.(gif|png)$」という正規表現では「HOME.GIF」のように大文字が使われているとマッチしなくなってしまいます。それじゃあと「\.(gif|GIF|png|PNG)$」と書いても「Clock.Gif」がマッチしません。

こんな時に使うのが「大文字小文字の同一視」です。あるいは、「大文字と小文字の違いを無視する」という言い方をします。

言語やツールによって書き方が違いますので、書き方をいくつか紹介します。

[Perl]
if ($filename =~ m/\.(gif|png)$/i) {
[Perl]
if ($filename =~ m/(?i)\.(gif|png)$/) {
[Perl]
if ($filename =~ m/(?i:\.(gif|png))$/) {
[JavaScript]
if (filename.match(/\.(gif|png)$/i)) {
[JavaScript]
var regexp = new RegExp('\.(gif|png)$', 'i');
if (filename.match(regexp)) {
[egrepコマンド]
\ls -1 | egrep -i '\.(gif|png)$'

ここでの練習はありませんので、各自の環境で練習して下さい。

どれか一文字(文字クラス)

選択を覚えた皆さんは次の正規表現はもう書けますね。「数字2桁にマッチする正規表現」。例えばこうです。「(0|1|2|3|4|5|6|7|8|9)(0|1|2|3|4|5|6|7|8|9)」。簡単ですね。それではアルファベット2文字にマッチする正規表現はどうでしょうか。一瞬で答が浮かびますけど、アルファベットの大文字と小文字あわせて52文字を並べるのはさすがに嫌でしょう? そんな貴方にお勧めなのが文字クラスです。指定した文字の中のどれか一文字を表します。

先ほどの「(0|1|2|3|4|5|6|7|8|9)(0|1|2|3|4|5|6|7|8|9)」は文字クラスを使うと「[0123456789][0123456789]」と書けます。縦棒がなくなったのでずいぶん楽になりました。「[0123456789]」で「0123456789のどれか一文字」を表しています。「[abc]」であれば「abcのどれか一文字」です。

だいぶ短くなったとはいえやっぱりまだ長い。ですのでこんな書き方も用意されています。「[0-9][0-9]」。ずいぶんすっきりしました。「[0-9]」で「0から9のどれか一文字」を表しています。この書き方を使うとアルファベット2文字にマッチする正規表現は「[a-zA-Z][a-zA-Z]」と書くことができます。

いったん整理しておきましょう。

選択 (0|1|2|3|4|5|6|7|8|9)
文字クラス [0123456789]
文字クラス(範囲指定) [0-9]

これでどれか一文字を表す正規表現を書けるようになりました。次はこれに挑戦。「数字以外の文字にマッチする正規表現」。仮に、出てくる文字が英数字のみということが保証されていれば「[a-zA-Z]」と書けば「数字以外の文字にマッチする正規表現」と同じ意味になります。ところが英数字のみが出てくることが保証される場合はあまりなくて、各種記号類も考慮した正規表現を書かなくてはいけません。

「~以外の文字にマッチする正規表現」を表す場合には文字クラスの先頭に「^」を書きます。「数字以外の文字にマッチする正規表現」であれば「[^0-9]」ですし、「アルファベット以外の文字にマッチする正規表現」であれば「[^a-zA-Z]」です。行頭を表す「^」と同じ記号を使いますので、意味を間違えないように注意して下さい。

文字クラスに関してはここまでに説明したものと、もう少しあとに紹介するピリオドを覚えていれば自分で書くには困ることはありませんが、略記法という便利な書き方もあるので覚えておきましょう。代表的なものを紹介します。

略記法 同等の表記 説明
\d [0-9] 数字
\D [^0-9] 非数字
\s [ \f\n\r\t\v] 空白文字
\S [^ \f\n\r\t\v] 非空白文字
\w [a-zA-Z0-9_] 単語構成文字
\W [^a-zA-Z0-9_] 非単語構成文字

上記で「同等の表記」として書いてあるのは一般的な場合で、ツールや言語によっては異なる場合があります。例えばPerlの場合「\s」は「[ \t\n\r\f]」と同等となっており「\v」は含みません。他にも例えば、Perlのutf8環境や.NETの規定の動作では「\d」が全角の数字にマッチするなど違いがありますので略記方を使う場合は注意してください。

ちなみに\f,\n,\r,\t,\vはそれぞれ改ページ・改行・キャリッジリターン・タブ・垂直タブを表します。

略記法は[]の中でも使えます。ですので英数字を表すのに「[a-zA-Z0-9]」と書く代わりに「[a-zA-Z\d]」とも書けます。

このほかにもう一つ覚えておきたいものがあります。それは、ピリオド(.)です。ピリオドは通常「改行以外の任意の文字」を表します(改行を含める場合もあります)。「何か3文字にマッチする正規表現」は「...」と書けます。

前に「\.(gif|png)$」という正規表現を紹介しました。この時は拡張子にマッチさせたかったので、ピリオドそのものだけにマッチするようにエスケープしていました。エスケープをせずに「.(gif|png)$」としてしまうと、ピリオドは任意の文字にマッチしますので例えば「tgif」にマッチすることになります。

一通り文字クラスについて見ましたので、練習をしましょう。

YYYY-MM-DD形式の文字列にマッチする正規表現を入力して下さい。ここでは書式のチェックだけするものとし、2008-99-99のような存在しない日付でもマッチして構いません。

入力
出力
回答入力:
\d\d\d\d-\d\d-\d\d

大文字のアルファベット3文字の行にマッチする正規表現を入力して下さい。

入力
出力
回答入力:
^[A-Z][A-Z][A-Z]$

数字以外の文字が含まれる行にマッチする正規表現を入力して下さい。

入力
出力
回答入力:
[^\d]

1文字の行にマッチする正規表現を入力して下さい。

入力
出力
回答入力:
^.$

繰り返し回数を指定する(量指定子)

練習問題で「大文字のアルファベット3文字の行にマッチする正規表現」というのを出題しました。回答例としては「^[A-Z][A-Z][A-Z]$」をあげています。これが3文字ではなくて50文字だったらどうでしょう。大変ですね。でも書こうと思えば書けます。それでは「3文字」ではなく「3文字以上」ではどうでしょうか。

3文字以上ということは最低3文字あればいいのだからというので「[A-Z][A-Z][A-Z]」というのが考えられます。たしかにこれで「ABC」や「DEFG」がマッチするようになるので良さそうにも思います。しかし「XYZ123」というのもマッチしてしまいます。

こんな時には量指定子を使います。例えば「大文字のアルファベット3文字以上の行にマッチする正規表現」であれば「^[A-Z]{3,}$」となります。

量指定子は「{m,n}」という形で書いて、「m回以上n回以下の繰り返し」を表します。nは省略可能です。先ほどの「^[A-Z]{3,}$」は「行頭、AからZが3回以上繰り返し、行末にマッチする正規表現」となります。また、丁度3文字というようにmとnが同じ場合には「{m}」と書けます。例えば「数字3文字のあとに英大文字2文字にマッチする正規表現」は「\d{3}[A-Z]{2}」となります。

よく使われるパターンについては記号が割り当てられています。

記号 同等の表記 説明
* {0,} 0回以上の繰り返し
+ {1,} 1回以上の繰り返し
? {0,1} 0回または1回

記号が割り当てられてるものについては、{m,n}形式ではなく記号を使うことが多いです。

一つ例を見てみましょう。先の例でPerlのスクリプトからコメント行にマッチする正規表現として「^#」としました。その例ではこれでも良かったのですが、実際のスクリプトではインデントされている場合があります。今回はインデントに対応しましょう。

if ($num % 2 == 0) {
    # 偶数の場合
    $num = $num / 2;
} else {
    # 奇数の場合
    $num = $num * 3 + 1;
}

インデントしている場合は「#」の前にいくつかの空白文字があります。インデントしていない場合も考慮すると空白文字が0個以上の後に「#」があることになります。これを正規表現で書くと「^\s*#」となります。

数字を書く時に3桁毎にカンマを入れる書き方がありますね。例えば1,234というように。今度はこのような数字の行にマッチする正規表現を見てみましょう。

123
1024
1,980
1,000,000
2.718
10,20

マッチするのは、数字が1~3個続いたあとにカンマ+数字3桁が0回以上繰り返しているものになります。例えば123であれば数字が3個続いてカンマ+数字3桁が0回繰り返していますし、1,000,000であれば数字が1個続いてカンマ+数字3桁が2回繰り返しています。

繰り返している箇所がまとまりになっている場合は、括弧を使ってグループ化します。今回の例では「^\d{1,3}(,\d{3})*$」となります。括弧を使って「カンマ+数字3桁」を表しています。

例も見たことですし、練習にしましょうか。

数字が含まれていない行にマッチする正規表現を入力して下さい。

入力
出力
回答入力:
^\D+$

GoogleやGooogleやGoooogleのようにGで始まって、oが2個以上続いて、gleで終わる文字列にマッチする正規表現を入力して下さい。

入力
出力
回答入力:
Go{2,}gle

拡張子.htmまたは拡張子.htmlのファイル名にマッチする正規表現を入力して下さい。

入力
出力
回答入力:
\.html?$

整数の行にマッチする正規表現を入力して下さい。

入力
出力
回答入力:
^[-+]?\d+$

文字列の長さが奇数になる行にマッチする正規表現を入力して下さい。

入力
出力
回答入力:
^.(..)*$

非欲張り量指定子

<B></B>で囲まれた部分にマッチする正規表現を考えてみましょう。

NFAに対応するDFAを<B>作れます</B>

<B>.+</B>」でマッチします。では、次の文字列が来た場合にどのようにマッチするでしょうか。

正規表現は<B>DFA</B>と<B>NFA</B>の2つと関係が深い

実は、次のようにマッチします。一つ目の</B>が「.+」の部分に飲み込まれます。

正規表現は<B>DFA</B>と<B>NFA</B>の2つと関係が深い

次のようにマッチして欲しい時もありますよね。

正規表現は<B>DFA</B>と<B>NFA</B>の2つと関係が深い

そんな時に登場するのが「非欲張り量指定子」です。書き方は簡単で量指定子の後ろに「?」を付けるだけ。今回の例であれば「<B>.+?</B>」です。

HTMLのタグを処理する時などに重宝しますので、覚えておいて下さい。

ここでの練習はありませんので、各自の環境で練習して下さい。

後方参照

次の文章を見てください。

今日こそあれをを買おう。
こんんばんは!

必ずというわけではありませんけど、同じ文字が続いている場合は入力間違いの可能性がありますよね。このような文字列にマッチする正規表現を見てみましょう。

例えば「(をを|んん)」とすれば上の二つの例には対応できます。しかし、他の文字は見逃してしまいます。かといって全部の文字を書き出すのは現実的ではありません。こういうときには後方参照を使います。

具体的には「(.)\1」とします。\1の部分が後方参照している箇所になります。参照元は括弧の中になります。\1の部分が括弧の中身に置き換わるというイメージです。

例えば、「今日」という部分に対して「(.)\1」がどうなるかというと、(.)の部分が「今」とマッチすると\1も「今」となるので「今今」とマッチするかを調べることになります。「今日」は「今今」とはマッチしませんので、この場合はマッチしないという結果になります。

「をを」についてはどうでしょうか。(.)の部分が「を」にマッチすると\1も「を」となるので「をを」とマッチするかを調べることになります。「をを」は「をを」とマッチしますので、この場合はマッチするという結果になります。

対象文字列 正規表現 調べる文字列
今日 (.)\1 今今
をを (.)\1 をを

後方参照は「\」に数字を付けたものになります。数字は何番目の括弧を見るかを表します。「\1」であれば一つ目の括弧の中身になりますし、「\2」であれば二つ目の括弧の中身になります。

対象文字列 正規表現 調べる文字列
いう (.)(.)\1 あい
(.)(.)\2 あい

それでは練習しましょう。

並び順を逆にしても同じ並びになる3文字の文字列が含まれる行にマッチする正規表現を入力してください。

入力
出力
回答入力:
(.).\1

並び順を逆にしても同じ並びになる5文字の文字列が含まれる行にマッチする正規表現を入力してください。

入力
出力
回答入力:
(.)(.).\2\1

同じ文字列を2回繰り返してできている行にマッチする正規表現を入力してください。

入力
出力
回答入力:
^(.+)\1$

終わりに

正規表現について基本的なものを紹介しました。これだけでも文字種チェックのための正規表現ぐらいは読み書きできるようになったと思います。実際に正規表現を使ったプログラムを書いたり、エディタの検索・置換で正規表現を使ってみて下さい。

身につけるためには実際に書くことが大事ですが、それと同じぐらい読むことも大事です。検索すればネット上でいろいろな正規表現が紹介されているので、ぜひ読んで意味まで読み取る練習をして下さい。

ここで紹介したのはほんの入り口の部分です。より詳しく知りたい方はネットで調べたり本を読んだりして下さい。本気で勉強するのであれば「詳説 正規表現」がお勧めです。

次のステップとして置換処理編もありますので、ぜひこちらもご覧下さい。

このページをきっかけに正規表現が使えるようになっていただければ幸いです。最後までご覧いただきありがとうございました。

スポンサーリンク