正規表現を始めよう!初心者向け基本解説
はじめに:正規表現とは何か? なぜ学ぶのか?
テキストデータを扱っていると、「特定のパターンに一致する文字列を見つけたい」「ある文字列を別の文字列に置き換えたい」「入力された文字列が特定の形式を満たしているかチェックしたい」といった場面によく遭遇します。例えば、
- たくさんのファイルの中から、特定の命名規則に従ったファイルを検索したい。
- 長い文章の中から、すべてのメールアドレスを抽出したい。
- Webサイトの入力フォームで、電話番号や郵便番号、パスワードが正しい形式で入力されているか確認したい。
- プログラムのソースコードで、特定の関数呼び出しパターンをまとめて修正したい。
- ログファイルから、特定のエラーメッセージを含む行だけを抜き出したい。
このような要求に応える強力なツールが、正規表現(Regular Expression、略してRegexやRegexp)です。
正規表現とは、文字列のパターンを記述するための特別な記法です。この記法を使うことで、「数字が3回繰り返された後にハイフンが続き、さらに数字が4回繰り返されるパターン」(例:「123-4567」のような電話番号の一部)や、「アルファベットで始まり、その後にアルファベット、数字、アンダースコアが任意の回数続くパターン」(例:「userName_1」のような変数名)などを、簡潔かつ正確に表現することができます。
正規表現は、ほとんどのプログラミング言語(Python、JavaScript、Ruby、PHP、Java、C#など)や、多くのテキストエディタ、コマンドラインツール(grep, sed, awkなど)で利用できます。一度正規表現の記法を覚えれば、様々なツールや言語で応用が効く、非常に汎用性の高いスキルと言えます。
最初は記号の羅列に見えて、まるで「呪文」のように感じるかもしれません。しかし、それぞれの記号が持つ意味を一つずつ理解していけば、決して難しいものではありません。本記事では、正規表現の基本の「き」から始め、よく使う記法や考え方を、豊富な例を交えながら丁寧に解説していきます。この記事を読めば、あなたも正規表現の第一歩を踏み出し、テキスト処理の可能性を大きく広げることができるでしょう。
さあ、正規表現の世界への扉を開きましょう!
正規表現の超基本:リテラル文字とマッチング
正規表現は、基本的に「対象となる文字列の中に、正規表現で指定したパターンに一致する部分があるか?」をチェックします。一致する部分が見つかれば「マッチした」と表現します。
最も基本的なパターンは、リテラル文字(Literal Characters)です。これは、その文字そのものにマッチする記号です。例えば、正規表現 a は、対象文字列の中にあるすべての a という文字にマッチします。
例1:単純なリテラル文字のマッチ
- 正規表現:
cat - 対象文字列:
"The cat sat on the mat." - マッチ結果:
"cat"(The **cat** sat on the mat.) にマッチ
この例では、正規表現 cat が対象文字列内の cat という連続した3文字に完全に一致するため、マッチが成立します。
- 正規表現:
dog - 対象文字列:
"The cat sat on the mat." - マッチ結果: マッチしない
対象文字列には dog という連続した3文字が含まれていないため、マッチしません。
- 正規表現:
at - 対象文字列:
"The cat sat on the mat." - マッチ結果:
"at"(The c**at** s**at** on the m**at**.) に3回マッチ
正規表現は、通常、対象文字列の中でパターンに一致する部分をすべて見つけようとします(ただし、ツールや設定によって最初のマッチだけを見つける場合もあります)。この例では、cat, sat, mat の中の at 部分にそれぞれマッチします。
このように、特別な意味を持たないアルファベット、数字、記号などのリテラル文字は、それ自身にマッチする正規表現となります。
正規表現を使う主な場面は以下の3つです。
- 検索: 特定のパターンに一致する文字列が対象の中に存在するかどうかを調べたり、存在する場合はその位置や内容を取得したりします。
- 置換: 特定のパターンに一致する文字列を、別の文字列に置き換えます。例えば、文章中の「古い単語」をすべて「新しい単語」に置き換えたり、特定の形式のデータを別の形式に変換したりします。
- バリデーション: 入力された文字列全体が、特定のパターン(例えば、メールアドレスの形式、パスワードの条件など)を満たしているかどうかを確認します。
この記事では主に「検索」(パターンにマッチするかどうか、どこがマッチするか)を中心に解説しますが、ここで学ぶ内容は置換やバリデーションにも直接応用できます。
正規表現の強力さ:メタ文字の理解
正規表現の真価は、特定のリテラル文字だけでなく、様々な種類の文字や繰り返し、位置などを指定できるメタ文字(Meta Characters)にあります。メタ文字は、文字そのものではなく、パターンの一部として特別な意味を持つ記号です。
代表的なメタ文字を見ていきましょう。
1. .: 任意の一文字
ドット(.)は、改行文字(\n)を除く任意の一文字にマッチします。これは「ワイルドカード」のようなものです。
例2:ドットの使用
- 正規表現:
c.t - 対象文字列:
"The cat, cot, cut, cet, c@t sat on the mat." - マッチ結果:
"cat", "cot", "cut", "cet", "c@t"(The **cat**, **cot**, **cut**, **cet**, **c@t** sat on the mat.) にマッチ
c と t の間に任意の一文字があればマッチします。スペースや記号など、改行以外のどんな文字でも . は受け入れます。
- 正規表現:
. - 対象文字列:
"abc 123" - マッチ結果:
"a", "b", "c", " ", "1", "2", "3"のそれぞれにマッチ (改行がなければすべての文字にマッチします)
2. *: 直前の要素の0回以上の繰り返し
アスタリスク(*)は、その直前の要素が0回以上繰り返されるパターンにマッチします。直前の要素とは、直前のリテラル文字、メタ文字、またはグループ化されたパターンなどです。
例3:アスタリスクの使用
- 正規表現:
a*b - 対象文字列:
"b", "ab", "aab", "aaab", "caab" - マッチ結果:
"b","ab","aab","aaab","aab"(**b**,**ab**,**aab**,**aaab**,c**aab**) にマッチ
aが0回(bのみ)、1回(ab)、2回(aab)、3回(aaab)繰り返された後にbが続くパターンにマッチします。caabの場合は、cの後のaabにマッチします。
- 正規表現:
.* - 対象文字列:
"Hello, world!" - マッチ結果:
"Hello, world!"(文字列全体にマッチ)
. は任意の一文字、* はその0回以上の繰り返しなので、.* は「改行を除く任意の一文字が0回以上続くパターン」、つまり改行を含まない任意の文字列にマッチします。これは非常に頻繁に使われるパターンです。
- 正規表現:
ab*c - 対象文字列:
"ac", "abc", "abbc", "abbbc", "axc" - マッチ結果:
"ac","abc","abbc","abbbc"(**ac**,**abc**,**abbc**,**abbbc**,axc) にマッチ
bが0回(ac)、1回(abc)、2回(abbc)、3回(abbbc)繰り返された後にcが続くパターンにマッチします。axcはbの繰り返しではないためマッチしません。
3. +: 直前の要素の1回以上の繰り返し
プラス(+)は、その直前の要素が1回以上繰り返されるパターンにマッチします。* との違いは、最低1回は出現する必要がある点です。
例4:プラスの使用
- 正規表現:
a+b - 対象文字列:
"b", "ab", "aab", "aaab", "caab" - マッチ結果:
"ab","aab","aaab","aab"(b,**ab**,**aab**,**aaab**,c**aab**) にマッチ
aが1回(ab)、2回(aab)、3回(aaab)繰り返された後にbが続くパターンにマッチします。bのみの場合はaが0回なのでマッチしません。
- 正規表現:
.+ - 対象文字列:
"Hello, world!" - マッチ結果:
"Hello, world!"(文字列全体にマッチ)
.+ は「改行を除く任意の一文字が1回以上続くパターン」、つまり改行を含まない1文字以上の任意の文字列にマッチします。
4. ?: 直前の要素の0回または1回の繰り返し
クエスチョンマーク(?)は、その直前の要素が0回または1回出現するパターンにマッチします。これは、「あってもなくてもよい」というOptionalな要素を表現するのに使われます。
例5:クエスチョンマークの使用
- 正規表現:
colou?r - 対象文字列:
"color","colour","coleur" - マッチ結果:
"color","colour"(**color**,**colour**,coleur) にマッチ
uが0回(color)または1回(colour)出現するパターンにマッチします。イギリス英語のcolourとアメリカ英語のcolorの両方にマッチさせたい場合などに便利です。
- 正規表現:
Nov(ember)? - 対象文字列:
"Nov 11","November 11" - マッチ結果:
"Nov","November"(**Nov** 11,**November** 11) にマッチ
(ember)というグループ全体が0回または1回出現するパターンにマッチします。これにより、Nov または November のどちらかにマッチさせることができます。
5. {n}, {n,}, {n,m}: 量指定子(Quantifiers)
*, +, ? は便利な量指定子ですが、回数を細かく指定したい場合があります。そのために波かっこ {} を使います。
{n}: 直前の要素がちょうど n 回繰り返されるパターンにマッチします。{n,}: 直前の要素がn 回以上繰り返されるパターンにマッチします。{n,m}: 直前の要素がn 回以上 m 回以下繰り返されるパターンにマッチします。
例6:量指定子の使用
- 正規表現:
a{3} - 対象文字列:
"a","aa","aaa","aaaa" -
マッチ結果:
"aaa"(a,aa,**aaa**,a**aaa**) にマッチ (aaaaの場合は最初の3つのaにマッチ) -
正規表現:
a{2,} - 対象文字列:
"a","aa","aaa","aaaa" - マッチ結果:
"aa","aaa","aaaa"(a,**aa**,**aaa**,**aaaa**) にマッチ
aが2回以上の繰り返しにマッチします。
- 正規表現:
a{2,4} - 対象文字列:
"a","aa","aaa","aaaa","aaaaa" - マッチ結果:
"aa","aaa","aaaa","aaaa"(a,**aa**,**aaa**,**aaaa**,**aaaa**a) にマッチ
aが2回以上4回以下の繰り返しにマッチします。aaaaaの場合は、最初の4つのaにマッチし、その後の1つのaにはマッチしません(または別のマッチとして処理されます)。
- 正規表現:
\d{3}-\d{4}(ここでは\dというメタ文字が出てきましたが、後述する「数字」を表します) - 対象文字列:
"郵便番号は123-4567です。また、0987-65-4321のような形式もあります。" - マッチ結果:
"123-4567"(郵便番号は**123-4567**です。また、0987-65-4321のような形式もあります。) にマッチ
これは「数字が3回、ハイフン、数字が4回」というパターンにマッチします。電話番号や郵便番号の一部の形式を表現する際によく使われます。
6. \: エスケープ文字
いくつかのメタ文字(., *, +, ?, ^, $, |, (, ), [, ], {, }, \) は、パターンの中で特別な意味を持ちます。もしこれらの文字そのものにマッチさせたい場合は、直前にバックスラッシュ(\)を付けてエスケープする必要があります。
例7:エスケープの使用
- 正規表現:
.(ドットは任意の一文字にマッチ) - 対象文字列:
"file.txt" -
マッチ結果:
"f", "i", "l", "e", ".", "t", "x", "t"のそれぞれにマッチ -
正規表現:
\.(バックスラッシュでドットをエスケープ) - 対象文字列:
"file.txt" - マッチ結果:
.(file**.**txt) にマッチ
これにより、ドットというリテラル文字そのものにマッチさせることができます。
- 正規表現:
\? - 対象文字列:
"Is it a question?" -
マッチ結果:
?(Is it a question**?**") にマッチ -
正規表現:
\\ - 対象文字列:
"C:\Users\user" - マッチ結果:
\(C:**\\**Users**\\**user) に2回マッチ
バックスラッシュそのものにマッチさせたい場合は、バックスラッシュをエスケープして \\ と記述します。
7. ^: 行頭(文字列の先頭)
キャレット(^)は、パターンが対象文字列の先頭(または複数行モードの場合は各行の先頭)にマッチすることを指定します。
例8:^の使用
- 正規表現:
^The - 対象文字列:
"The quick brown fox jumps over the lazy dog.\nThe dog sat on the mat." - マッチ結果:
"The"(**The** quick brown fox jumps over the lazy dog.\nThe dog sat on the mat.) にマッチ
対象文字列の最初の行頭にある The にマッチします。2行目の The は行頭ではありません(複数行モードでない限り)。
- 正規表現:
^cat - 対象文字列:
"The cat sat on the mat." - マッチ結果: マッチしない
catは文字列の先頭にないためマッチしません。
8. $: 行末(文字列の末尾)
ドル記号($)は、パターンが対象文字列の末尾(または複数行モードの場合は各行の末尾)にマッチすることを指定します。
例9:$の使用
- 正規表現:
dog$ - 対象文字列:
"The quick brown fox jumps over the lazy dog.\nThe dog sat on the mat." - マッチ結果:
"dog"(The quick brown fox jumps over the lazy lazy **dog**.\nThe dog sat on the mat.) にマッチ
対象文字列の最後の dog にマッチします。これは文字列の末尾にあるためです。
- 正規表現:
mat.$ - 対象文字列:
"The cat sat on the mat." - マッチ結果:
mat.(The cat sat on the **mat.**) にマッチ
.は任意の一文字なので、mat. は mat の後に任意の一文字(ここではピリオド)が来て、それが文字列の末尾にあるパターンにマッチします。
^と$を組み合わせて使うと、文字列全体が特定のパターンと完全に一致するかどうかを確認できます。これは入力値のバリデーションでよく使われます。
例10:^と$の組み合わせ(文字列全体のバリデーション)
- 正規表現:
^\d{3}-\d{4}$ - 対象文字列:
"123-4567"-> マッチ (**123-4567**)"郵便番号は123-4567です。"-> マッチしない (123-4567という部分はありますが、文字列全体がパターンと一致していません)"98765-4321"-> マッチしない (桁数が異なります)
この正規表現は、「文字列全体が、数字3桁、ハイフン、数字4桁の形式である」という条件を満たす場合にのみマッチします。
9. |: OR条件
パイプ(|)は、左側のパターンまたは右側のパターンのいずれかにマッチすることを指定します。複数のパターンからどれかにマッチさせたい場合に便利です。
例11:|の使用
- 正規表現:
cat|dog - 対象文字列:
"The cat or the dog." - マッチ結果:
"cat","dog"(The **cat** or the **dog**.) にマッチ
cat または dog のいずれかにマッチします。
- 正規表現:
(apple|banana|orange) juice - 対象文字列:
"I like apple juice and orange juice." - マッチ結果:
"apple juice","orange juice"(I like **apple juice** and **orange juice**.) にマッチ
|は、デフォルトでは正規表現全体に適用されますが、( )を使ってグループ化することで、適用範囲を限定できます。この例では、(apple|banana|orange)で「appleまたはbananaまたはorange」というグループを作り、その後にjuiceが続くパターンにマッチさせています。(apple|banana|orange) juice は apple juice または banana juice または orange juice のいずれかにマッチすることを意味します。もしグループ化しない apple|banana|orange juice と書くと、「apple」または「banana」または「orange juice」という意味になってしまうので注意が必要です。
文字クラス:特定の文字集合にマッチ
特定の文字グループの中から任意の一文字にマッチさせたい場合があります。例えば、「母音のいずれかにマッチ」「数字のいずれかにマッチ」「アルファベットのいずれかにマッチ」といった場合です。これを実現するのが文字クラス(Character Classes)です。文字クラスは角かっこ [] で囲んで表現します。
1. [...]: 指定した文字集合のいずれか一文字
角かっこ [] の中に文字を並べると、その中のいずれか一文字にマッチします。
例12:文字クラスの使用
- 正規表現:
[aeiou] - 対象文字列:
"Hello, world!" - マッチ結果:
"e","o","o"(H**e**ll**o**, w**o**rld!) にマッチ
指定した母音のいずれか一文字にマッチします。
- 正規表現:
[0123456789] - 対象文字列:
"Page 123" - マッチ結果:
"1","2","3"(Page **123**) のそれぞれにマッチ
これは、数字のいずれか一文字にマッチします。
文字クラスの中では、いくつかのメタ文字は特別な意味を失います(., *, +, ?, |, (, ))。ただし、-(範囲指定)、^(否定)、](文字クラスの終了)、\(エスケープ)は特別な意味を持ちます。
文字の範囲指定: 文字クラスの中では、ハイフン - を使うと、文字の範囲を指定できます。例えば、[0-9] は [0123456789] と同じ意味で、「0から9までのいずれかの数字」にマッチします。
[a-z]: 小文字アルファベットのいずれか[A-Z]: 大文字アルファベットのいずれか[a-zA-Z]: 大文字小文字アルファベットのいずれか[0-9]: 数字のいずれか[a-zA-Z0-9]: アルファベットまたは数字のいずれか
例13:文字クラスの範囲指定
- 正規表現:
[a-zA-Z]+ - 対象文字列:
"Hello 123 World" - マッチ結果:
"Hello","World"(**Hello** 123 **World**) にマッチ
[a-zA-Z]でアルファベット一文字にマッチし、+でそれが1回以上繰り返されるパターン、つまり「1文字以上のアルファベットの並び(単語)」にマッチします。
2. [^...]: 否定文字クラス
文字クラスの開き角かっこ [ の直後にキャレット ^ を置くと、指定した文字集合以外の文字のいずれか一文字にマッチします。これは「否定」の意味になります。
例14:否定文字クラスの使用
- 正規表現:
[^aeiou] - 対象文字列:
"Hello, world!" - マッチ結果:
"H", "l", "l", ",", " ", "w", "r", "l", "d", "!"(**H**e**ll**o**,\*\* **w**o**rld!**) のそれぞれにマッチ
母音以外のすべての文字にマッチします。
- 正規表現:
[^0-9]または[^\d](ショートハンド\dは後述) - 対象文字列:
"Page 123" - マッチ結果:
"P", "a", "g", "e", " "(**Page\*\* **123**) のそれぞれにマッチ
数字以外のすべての文字にマッチします。
3. よく使うショートハンド文字クラス
正規表現には、よく使われる文字クラスをより簡潔に表現するためのショートハンド(Short Hand)がいくつか用意されています。これらはバックスラッシュ \ で始まります。
\d: 数字 (Digit)。[0-9]と同じ意味です。\D: 数字以外 (Non-digit)。[^0-9]と同じ意味です。
\w: 単語構成文字 (Word character)。アルファベット、数字、アンダースコア_のいずれか。[a-zA-Z0-9_]と同じ意味です。\W: 単語構成文字以外 (Non-word character)。[^a-zA-Z0-9_]と同じ意味です。
\s: 空白文字 (Whitespace character)。スペース、タブ\t、改行\n、キャリッジリターン\r、フォームフィード\f、垂直タブ\vのいずれか。\S: 空白文字以外 (Non-whitespace character)。[^\s]と同じ意味です。
例15:ショートハンド文字クラスの使用
- 正規表現:
\d+ - 対象文字列:
"User ID: 12345, Order No: 987" - マッチ結果:
"12345","987"(User ID: **12345**, Order No: **987**) にマッチ
1つ以上の数字の並びにマッチします。
- 正規表現:
\w+ - 対象文字列:
"Hello_world 123!" - マッチ結果:
"Hello_world","123"(**Hello_world** **123**!) にマッチ
1つ以上の単語構成文字の並びにマッチします。Hello_world と 123 がそれぞれ一つの単語として認識されています。
- 正規表現:
\s - 対象文字列:
"Hello World" -
マッチ結果:
" "(Hello**\s**World) にマッチ (スペースにマッチ) -
正規表現:
\S+ - 対象文字列:
"Hello World" - マッチ結果:
"Hello","World"(**Hello** **World**) にマッチ
1つ以上の空白文字以外の並びにマッチします。これにより、スペースで区切られた単語ごとにマッチさせることができます。
これらのショートハンドを使うと、正規表現をより短く、分かりやすく書くことができます。
グループ化とキャプチャ:パターンの一部を取り出す
丸かっこ () は、主に以下の二つの目的で使われます。
- グループ化: 複数の要素を一つのまとまりとして扱い、量指定子を適用したり、
|の適用範囲を限定したりします。 - キャプチャ: マッチした文字列全体だけでなく、グループ化されたパターンの一部を個別に後から取り出せるようにします。これをキャプチャ(Capture)と呼びます。キャプチャされた部分は「キャプチャグループ」と呼ばれ、通常は正規表現エンジンによって番号が振られます(左から順に1, 2, 3…)。
1. (...): グループ化とキャプチャ
例16:グループ化(量指定子と組み合わせ)
- 正規表現:
(ab)+ - 対象文字列:
"ab","abab","abcab" - マッチ結果:
"ab","abab","ab"(**ab**,**abab**,abc**ab**) にマッチ
(ab)でabという連続した2文字を一つのグループとして扱い、+でそのグループが1回以上繰り返されるパターンにマッチします。もしグループ化しない ab+ だと、「aの後にbが1回以上続く」という意味になり、abb, abbbなどにマッチします。
例17:グループ化(OR条件と組み合わせ)
- 正規表現:
(cat|dog) food - 対象文字列:
"cat food","dog food","bird food" - マッチ結果:
"cat food","dog food"(**cat food**,**dog food**,bird food) にマッチ
前述のOR条件の例と同じく、()でcat|dogの適用範囲を限定し、cat foodまたはdog foodにマッチさせます。
例18:キャプチャ(マッチした部分の取り出し)
- 正規表現:
(\d{4})-(\d{2})-(\d{2}) - 対象文字列:
"Today's date is 2023-10-27." - マッチ結果:
"2023-10-27"にマッチ。さらに以下のキャプチャグループが作成されます。- グループ1:
"2023"(年) - グループ2:
"10"(月) - グループ3:
"27"(日)
- グループ1:
この正規表現は「YYYY-MM-DD」形式の日付にマッチし、さらに年、月、日をそれぞれ個別のグループとしてキャプチャしています。多くの正規表現エンジンやプログラミング言語では、マッチ全体だけでなく、これらの個別のグループの内容を後から参照したり、変数に取り込んだり、置換後の文字列で利用したりできます。
例えば、置換機能で「YYYY-MM-DD」形式を「MM/DD/YYYY」形式に変換したい場合、この正規表現と置換パターン \2/\3/\1 (または $2/$3/$1 のように言語によって記法は異なる) を組み合わせることで実現できます。\1, \2, \3 はそれぞれ1番目、2番目、3番目のキャプチャグループの内容を指します。
2. (?:...): 非キャプチャグループ
単にグループ化して量指定子やORの範囲を限定したいだけで、そのグループの内容を後から取り出す必要がない(キャプチャする必要がない)場合があります。このような場合は、(?:...) という構文を使うことで、キャプチャを行わないグループを作成できます。これを非キャプチャグループ(Non-capturing Group)と呼びます。
非キャプチャグループを使う利点は、パフォーマンスのわずかな向上や、キャプチャグループの番号付けをシンプルに保てることなどがあります。
例19:非キャプチャグループの使用
- 正規表現:
(?:cat|dog) food - 対象文字列:
"cat food" - マッチ結果:
"cat food"にマッチ。キャプチャグループは作成されません(マッチ全体は取得できます)。
(cat|dog) food とマッチ結果は同じですが、(cat|dog)の部分はキャプチャグループとして扱われません。
3. 後方参照(Backreferences)
キャプチャグループでキャプチャした内容を、正規表現パターンの後ろの方で再利用することができます。これを後方参照と呼び、\1, \2, \3, … のように、対応するキャプチャグループの番号をバックスラッシュの後に付けて記述します。
例20:後方参照の使用
- 正規表現:
([a-zA-Z]+) \1 - 対象文字列:
"This is a test test string." - マッチ結果:
"test test"(This is a **test test** string.) にマッチ
([a-zA-Z]+) は1つ以上のアルファベットの並びをキャプチャします(グループ1)。その後の \1 は、グループ1でキャプチャされた内容と全く同じ文字列にマッチすることを意味します。したがって、この正規表現は「同じ単語がスペースを挟んで繰り返されているパターン」にマッチします。
- 正規表現:
<(\w+)>.*?</\1> - 対象文字列:
"<h1>Title</h1><p>Paragraph</p>" - マッチ結果:
"<h1>Title</h1>","<p>Paragraph</p>"(**<h1>Title</h1>**\n**<p>Paragraph</p>**) にマッチ
これは簡易的なHTML/XMLタグのマッチングの例です。
<(w+)> は < の後に1つ以上の単語構成文字が続き、> で閉じるパターンにマッチし、タグ名(例えば h1 や p)をグループ1としてキャプチャします。
.*? は「任意文字が0回以上繰り返し」ですが、後述する非貪欲マッチを使うことで、次のパターンまでの最短一致を試みます(これがないと <p>Paragraph</p> 全体ではなく、<h1>Title</h1><p> の <p>までマッチしてしまう可能性があります)。
</\1> は </ の後に、グループ1でキャプチャされた内容(つまり開始タグと同じタグ名)が続き、> で閉じるパターンにマッチします。
これにより、開始タグと終了タグのタグ名が一致する要素全体にマッチさせることができます。ただし、このパターンはネストしたタグや属性、コメントなどに対応できないため、本格的なHTML解析には正規表現は不向きです。
後方参照は、特定のパターンが繰り返される箇所や、開始と終了が対応しているようなパターンを見つけるのに役立ちます。
先読み・後読み(Lookahead & Lookbehind):マッチ位置の条件指定
正規表現の中には、実際に文字列にマッチするわけではないが、特定の条件を満たす「位置」にマッチするかどうかをチェックする構文があります。これをアサーション(Assertion)と呼びます。その中でもよく使われるのが先読み(Lookahead)と後読み(Lookbehind)です。これらは、あるパターンの「直前」や「直後」に特定の文字列が存在するかどうかを調べるのに使われますが、その直前や直後の文字列自体はマッチ結果に含まれません。
構文は以下の4種類があります。
(?=...): 肯定先読み(Positive Lookahead)。...で指定されたパターンが直後にある位置にマッチします。...の部分はマッチ結果に含まれません。(?!...): 否定先読み(Negative Lookahead)。...で指定されたパターンが直後にない位置にマッチします。...の部分はマッチ結果に含まれません。(?<=...): 肯定後読み(Positive Lookbehind)。...で指定されたパターンが直前にある位置にマッチします。...の部分はマッチ結果に含まれません。(?<!...): 否定後読み(Negative Lookbehind)。...で指定されたパターンが直前にない位置にマッチします。...の部分はマッチ結果に含まれません。
※ 後読みは、一部の正規表現エンジン(特に古い実装やJavaScriptなど)では、パターン ... の長さが固定である必要があるなどの制限がある場合があります。
1. (?=...): 肯定先読み
特定の文字列が続く位置を探したいが、その特定の文字列自体はマッチ結果に含めたくない場合に便利です。
例21:肯定先読みの使用
- 正規表現:
Windows(?=XP|Vista|7|8|10) - 対象文字列:
"I use Windows7 and WindowsXP." - マッチ結果: 最初の
Windows(I use **Windows**7 and WindowsXP.) にマッチ
この正規表現は、「Windows という文字列にマッチする」という部分と、「その Windows の直後に XP または Vista または 7 または 8 または 10 が続く」という条件を組み合わせています。マッチするのはあくまで Windows という文字列そのものですが、そのマッチが発生するのは直後が指定したOS名のいずれかである場合に限られます。WindowsXP の Windows にはマッチしますが、XP はマッチ結果には含まれません。
2. (?!...): 否定先読み
特定の文字列が続かない位置を探したい場合に便利です。
例22:否定先読みの使用
- 正規表現:
Windows(?!XP|Vista|7|8|10) - 対象文字列:
"I use Windows and Windows11." - マッチ結果: 最初の
Windows(I use **Windows** and Windows11.) にマッチ
この正規表現は、「Windows という文字列にマッチする」という部分と、「その Windows の直後に XP または Vista または 7 または 8 または 10 が続かない」という条件を組み合わせています。Windows の直後に何も続いていない場合や、11 が続いている場合にはマッチしますが、XP や 7 が続いている場合にはマッチしません。
3. (?<=...): 肯定後読み
特定の文字列の直後の位置を探したいが、その特定の文字列自体はマッチ結果に含めたくない場合に便利です。
例23:肯定後読みの使用
- 正規表現:
(?<=\$)\d+(\.\d{2})? - 対象文字列:
"Price: $19.99, Discount: 5%" - マッチ結果:
"19.99"(Price: $**19.99**, Discount: 5%) にマッチ
この正規表現は、「$ の直後にある」という条件を満たす位置にマッチします。その位置から始まる \d+(1回以上の数字)とそれに続く (\.\d{2})?(小数点以下の数字2桁が0回または1回)にマッチします。結果として、$ に続く金額だけを抜き出すことができますが、$ 自体はマッチ結果に含まれません。
4. (?<!...): 否定後読み
特定の文字列の直後ではない位置を探したい場合に便利です。
例24:否定後読みの使用
- 正規表現:
(?<!Dr\.) [A-Z][a-zA-Z]+ - 対象文字列:
"Mr. Smith and Dr. Jones" - マッチ結果:
"Smith"(Mr. **Smith** and Dr. Jones) にマッチ
この正規表現は、「Dr. の直後ではない」という条件を満たす位置から始まる「大文字アルファベット1文字、その後にアルファベットが1回以上続く」パターンにマッチします。Mr. の後の Smith にはマッチしますが、Dr. の後の Jones にはマッチしません。
先読み・後読みは、より複雑で precise なマッチングを行う際に非常に役立ちます。ただし、最初は少し難しく感じるかもしれません。まずは基本的なメタ文字や文字クラスをしっかりマスターしてから取り組むのが良いでしょう。
フラグ(オプション):マッチングの振る舞いを変更する
正規表現によるマッチングの振る舞いは、フラグ(Flags)またはオプション(Options)を指定することで変更できます。どのフラグが利用できるかは正規表現エンジンやツールによって異なりますが、いくつかの共通してよく使われるフラグがあります。
フラグは通常、正規表現パターンの末尾に /pattern/flags のようにスラッシュの後に記述するか、プログラミング言語の関数やメソッドの引数として指定します。
代表的なフラグ:
i: Ignore case (大文字小文字を区別しない)- 通常、正規表現はアルファベットの大文字小文字を区別します。このフラグを指定すると、大文字と小文字を区別せずにマッチングを行います。
g: Global (すべて一致させる)- 多くの正規表現エンジンは、デフォルトでは最初に見つかったマッチで処理を終了します。このフラグを指定すると、対象文字列の中でパターンに一致するすべての箇所を検索・取得します。検索や置換で非常に頻繁に使われます。
m: Multiline (複数行モード)- 通常、
^は文字列全体の先頭、$は文字列全体の末尾にのみマッチします。このフラグを指定すると、^は各行の先頭(改行\nの直後)、$は各行の末尾(改行\nの直前、および文字列全体の末尾)にもマッチするようになります。
- 通常、
s: Dotall (ドットが改行にもマッチ)- 通常、
.は改行文字\n以外の任意の一文字にマッチします。このフラグを指定すると、.が改行文字を含むすべての文字にマッチするようになります。
- 通常、
u: Unicode (Unicodeモード)- Unicode文字を正しく扱うためのモードです。特に、
\w,\d,\sなどのショートハンドや、文字クラスの範囲指定がUnicodeの文字プロパティを考慮するようになります。現代のテキスト処理では重要になることが多いです。
- Unicode文字を正しく扱うためのモードです。特に、
y: Sticky (粘着マッチ、JavaScript特有)- 文字列の特定の開始位置からのみマッチを試みます。
例25:フラグの使用
- 正規表現:
/apple/i(パターンappleとフラグi) - 対象文字列:
"Apple and apple." - マッチ結果:
"Apple","apple"(**Apple** and **apple**.) にマッチ
iフラグにより、大文字小文字を区別せず、Appleにもappleにもマッチします。
- 正規表現:
/cat/g(パターンcatとフラグg) - 対象文字列:
"cat dog cat bird cat." - マッチ結果:
"cat","cat","cat"(**cat** dog **cat** bird **cat**.) に3回マッチ
gフラグにより、すべての一致箇所を取得します。もしgフラグがないと、最初のcatにだけマッチして終了することがあります。
- 正規表現:
/^abc$/m(パターン^abc$とフラグm) - 対象文字列:
"123\nabc\n456" - マッチ結果:
"abc"(123\n**abc**\n456) にマッチ
mフラグがない場合、^は文字列全体の先頭、$は文字列全体の末尾にしかマッチしないため、このパターンはマッチしません。mフラグがあると、改行に挟まれたabcが行全体として認識され、先頭の^と末尾の$にマッチします。
- 正規表現:
/a.*b/s(パターンa.*bとフラグs) - 対象文字列:
"a\nb" - マッチ結果:
"a\nb"(**a\nb**) にマッチ
sフラグがない場合、.は改行\nにマッチしないため、aとbの間に改行があるとマッチしません。sフラグがあると、.が改行にもマッチするため、aからbまでの間の改行を含んだ文字列全体にマッチします。
これらのフラグを組み合わせることも可能です(例: /pattern/gi)。ツールや言語によって利用できるフラグやその名称が異なる場合があるので、使用する環境のドキュメントを確認してください。
貪欲マッチと非貪欲マッチ:繰り返しの範囲指定
量指定子(*, +, ?, {n,}, {n,m})を使う際、マッチする繰り返し回数に幅がある場合、正規表現エンジンはデフォルトでできるだけ長くマッチする方を優先します。この振る舞いを貪欲マッチ(Greedy Match)と呼びます。
しかし、場合によってはできるだけ短くマッチさせたいことがあります。このような場合は、量指定子の直後に ? を付けることで、その量指定子を非貪欲マッチ(Non-greedy Match)または最小マッチ(Lazy Match)に変更できます。
非貪欲な量指定子:
*?: 直前の要素の0回以上の繰り返し(最短一致)+?: 直前の要素の1回以上の繰り返し(最短一致)??: 直前の要素の0回または1回の繰り返し(最短一致){n,m}?: 直前の要素のn回以上m回以下の繰り返し(最短一致){n,}?: 直前の要素のn回以上の繰り返し(最短一致)
例26:貪欲マッチ vs 非貪欲マッチ
HTMLタグの例で見てみましょう。
- 正規表現(貪欲):
<.*> - 対象文字列:
<b>bold</b><i>italic</i> - マッチ結果:
<b>bold</b><i>italic</i>(**<b>bold</b><i>italic</i>**) にマッチ
.は任意の一文字、*はその0回以上の繰り返し、全体として<から>までのパターンを探します。貪欲マッチなので、可能な限り長くマッチしようとします。最初の< (<b>) から最後の> (</i>) までが対象となり、文字列全体にマッチしてしまいます。これは意図しない結果であることが多いです。
- 正規表現(非貪欲):
<.*?> - 対象文字列:
<b>bold</b><i>italic</i> - マッチ結果:
<b>bold</b>,<i>italic</i>(**<b>bold</b>****<i>italic</i>**) にそれぞれマッチ
量指定子*の後に?を付けた非貪欲マッチです。これにより、.の繰り返しは可能な限り短くなります。最初の< (<b>) から最初の> (</b>) までで一旦マッチが成立し、そこで終了します。次に残りの文字列から同様のパターンを探し、< (<i>) から最初の> (</i>) までにマッチします。これにより、個々のタグ要素に正しくマッチさせることができました。
もう一つ例を見てみましょう。
例27:貪欲マッチ vs 非貪欲マッチ(別例)
- 正規表現(貪欲):
a.+b - 対象文字列:
"a123b456b" - マッチ結果:
"a123b456b"(**a123b456b**) にマッチ
aの後に任意の一文字が1回以上続き、bが続くパターン。貪欲マッチなので、可能な限り長くマッチしようとします。最初のaから最後のbまで全体にマッチします。
- 正規表現(非貪欲):
a.+?b - 対象文字列:
"a123b456b" - マッチ結果:
"a123b"(**a123b**456b) にマッチ
非貪欲マッチなので、.+?は可能な限り短くマッチしようとします。最初のaから始まり、最小限の繰り返しで次のbに到達するまでを探します。結果としてa123bにマッチします。
HTMLやXMLのようなマークアップ言語、あるいは特定の区切り文字に囲まれたデータを扱う際には、非貪欲マッチが必要になる場面が多いです。意図通りのマッチングを行うためには、この貪欲・非貪欲の概念と使い分けを理解することが重要です。
実践的な使い方と例
これまでに学んだメタ文字、文字クラス、量指定子、グループ化、フラグなどを組み合わせて、より実用的なパターンを作成してみましょう。
例28:簡単なメールアドレスの検証
メールアドレスの一般的な形式は「ユーザー名@ドメイン名」です。ユーザー名やドメイン名に使われる文字には一定の規則があります。簡易的なパターンを作成します。
- 正規表現:
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ - 解説:
^: 文字列の先頭[a-zA-Z0-9._%+-]+: アルファベット(大小)、数字、.,_,%,+,-のいずれかが1回以上 (+) 続くパターン。これがユーザー名部分に相当します。@:@というリテラル文字[a-zA-Z0-9.-]+: アルファベット(大小)、数字、.,-のいずれかが1回以上 (+) 続くパターン。これがドメイン名部分(.で区切られた部分を含む)に相当します。\.: ドット.というリテラル文字(エスケープが必要)[a-zA-Z]{2,}: アルファベット(大小)が2回以上 ({2,}) 続くパターン。これがトップレベルドメイン(例:com,org,jpなど)に相当します。$: 文字列の末尾
- 対象文字列と結果:
"[email protected]"-> マッチ (**[email protected]**)"[email protected]"-> マッチ (**[email protected]**)"[email protected]"-> マッチ (**[email protected]**)"user@localhost"-> マッチしない(トップレベルドメイン部分が.{2,}のパターンに合わない)"[email protected]"-> マッチしない(ドメイン名部分が[a-zA-Z0-9.-]+のパターンに合わない –.が最初に来ている)"[email protected]"-> マッチしない(ドメイン名部分に..が含まれている)" [email protected] "-> マッチしない(前後にスペースがある –^と$があるため)
注意点: このパターンはあくまで「一般的な」メールアドレスの形式を捉えた簡易的なものです。RFCに厳密に従ったメールアドレスの正規表現は非常に複雑になり、可読性が著しく低下します。実用上は、このような簡易パターンである程度絞り込み、最終的な検証はメール送信による確認など、より確実な方法と組み合わせるのが一般的です。
例29:電話番号の検索(複数の形式に対応)
「012-345-6789」のようなハイフン区切りと、「0123456789」のようなハイフンなしの両方に対応するパターンを作成します。
- 正規表現:
\d{2,5}-?\d{1,4}-?\d{4} - 解説:
\d{2,5}: 数字が2桁以上5桁以下。市外局番などを想定。-?: ハイフンが0回または1回。ハイフンがあってもなくてもよい。\d{1,4}: 数字が1桁以上4桁以下。市中局番などを想定。-?: ハイフンが0回または1回。\d{4}: 数字がちょうど4桁。加入者番号などを想定。
- 対象文字列と結果:
"電話番号は03-1234-5678です。携帯は09012345678です。"- マッチ結果:
"03-1234-5678","09012345678"(電話番号は**03-1234-5678**です。携帯は**09012345678**です。) にマッチ
このパターンは日本の電話番号の一部形式に対応していますが、国際電話番号や特殊な番号形式には対応していません。また、「090-12345-678」のような不正な形式にもマッチしてしまう可能性があります。より厳密なパターンを作成するには、地域の番号体系などを考慮する必要があります。
例30:特定のファイル名の検索
「report_」で始まり、その後に日付(YYYYMMDD形式)、そして「.csv」で終わるファイル名を検索します。
- 正規表現:
^report_\d{8}\.csv$ - 解説:
^: 文字列の先頭report_:report_というリテラル文字列\d{8}: 数字がちょうど8桁(YYYYMMDD形式)\.: ドット.というリテラル文字csv:csvというリテラル文字列$: 文字列の末尾
- 対象文字列と結果:
"report_20231027.csv"-> マッチ (**report_20231027.csv**)"report_2023-10-27.csv"-> マッチしない(ハイフンがあるため)"report_20231027.txt"-> マッチしない(拡張子が異なるため)"old_report_20231027.csv"-> マッチしない(先頭が異なるため)
^と$を使うことで、ファイル名全体がこのパターンと一致する場合にのみマッチします。
例31:置換機能を使ったデータ形式の変換
「姓,名」の形式になっている氏名リストを、「名 姓」の形式に変換したい場合。
- 正規表現:
^([^,]+),(.+)$ - 解説:
^: 文字列の先頭([^,]+): カンマ,以外の文字[^,]が1回以上+続くパターンをキャプチャします(グループ1)。これが「姓」に相当すると考えられます。,: カンマ,というリテラル文字。(.+): 任意の一文字.が1回以上+続くパターンをキャプチャします(グループ2)。これが「名」に相当すると考えられます。$: 文字列の末尾
- 置換パターン:
$2 $1(または\2 \1)- グループ2(名)の内容、スペース、グループ1(姓)の内容
- 対象文字列:
"山田,太郎" - マッチ結果:
"山田,太郎"にマッチ。グループ1="山田", グループ2="太郎" - 置換後の文字列:
"太郎 山田"
このように、キャプチャグループと後方参照(置換パターンでの参照)を組み合わせることで、複雑な文字列の置換処理を効率的に行うことができます。
例32:URLからのパス部分の抽出(肯定先読み/後読みの利用)
https://www.example.com/path/to/page?query=value のようなURLから、/path/to/page の部分だけを抽出したい場合。
- 正規表現:
(?<=https?://[^/]+/)[^?#]+ - 解説:
(?<=...): 肯定後読み開始https?://:http://またはhttps://[^/]+: スラッシュ/以外の文字が1回以上 (+) 続く。これはドメイン名部分に相当します。/: ドメイン名の後の最初のスラッシュ/
): 肯定後読み終了。ここまでのパターンが直前にある位置にマッチします。つまり、https://www.example.com/の直後の位置です。[^?#]+: その位置から始まる、クエスチョンマーク?またはシャープ#以外の文字が1回以上 (+) 続くパターンにマッチします。これがパス部分に相当します。
- 対象文字列:
"https://www.example.com/path/to/page?query=value" - マッチ結果:
"/path/to/page"(https://www.example.com**\/path\/to\/page**?query=value) にマッチ
後読み (?<=...) を使うことで、マッチ結果に https://www.example.com/ の部分を含めずに、パス部分だけを抽出できています。
これらの例からわかるように、正規表現は様々なテキスト処理の場面で非常に強力なツールとなります。最初は簡単なパターンから始め、徐々に複雑なパターンを組み立てていくことで、その能力を最大限に引き出せるようになります。
正規表現を使うツールやプログラミング言語
正規表現は、特定のソフトウェアや言語だけで使えるものではなく、非常に多くの環境でサポートされています。
- テキストエディタ:
- VS Code, Sublime Text, Atom, Notepad++, Sakura Editor など、多くのモダンなテキストエディタは正規表現を使った検索・置換機能を搭載しています。これは日常的なテキスト編集で非常に役立ちます。
- コマンドラインツール:
grep: ファイルの中から特定のパターンを含む行を検索するのに使います。sed: ストリームエディタ。ファイルや標準入力のテキストを正規表現で置換するのによく使われます。awk: テキスト処理ツール。正規表現でパターンを指定し、一致する行に対して処理を行うことができます。
-
プログラミング言語:
- Python:
reモジュールを提供。re.search(),re.match(),re.findall(),re.sub()など、豊富な関数があります。 - JavaScript:
RegExpオブジェクトまたはリテラル構文/pattern/flagsで扱います。Stringオブジェクトのmatch(),search(),replace(),split()メソッドでも正規表現が使えます。 - Ruby:
/pattern/flagsのリテラル構文が使えます。StringクラスやRegexpクラスに多くのメソッドがあります。 - PHP:
preg_match(),preg_replace()など、preg_*関数群でPCRE (Perl Compatible Regular Expressions) 互換の正規表現を扱います。 - Java:
java.util.regexパッケージを使います。PatternクラスとMatcherクラスが中心です。 - C#:
System.Text.RegularExpressions名前空間を使います。Regexクラスが中心です。 - その他多数の言語でサポートされています。
- Python:
-
オンラインテスター/デバッガー:
- regex101.com: 正規表現パターン、対象文字列、使用する正規表現エンジン(PCRE, JavaScript, Python, Goなど)を選択して、リアルタイムにマッチ結果を確認できます。各部分が何にマッチしているかの詳細な解説や、置換機能のテスト、パフォーマンス分析なども可能です。初心者が正規表現を試したり学習したりするのに非常におすすめです。
- regexr.com: こちらも人気の高いオンラインツールです。パターンを入力するとすぐにマッチ結果が表示され、リファレンスや例も豊富です。
これらのツールや言語によって、正規表現の記法や利用できるフラグ、機能(特に後読みや複雑なアサーションなど)に微妙な「方言」や互換性の違いがある場合があります(例: PCRE, Emacs Regex, POSIX Regex, JavaScript Regexなど)。しかし、今回解説した基本的なメタ文字や構文はほとんど共通して利用できます。
正規表現の注意点・落とし穴
正規表現は非常に強力ですが、使い方を誤ると意図しない結果になったり、パフォーマンスの問題を引き起こしたりすることがあります。
- 可読性の低下: 複雑な正規表現は、記号の羅列になりがちで、書いた本人ですら後から見返すと意味を理解するのが難しくなることがあります。コメントを付けたり、小さなパターンに分割したり、無理に正規表現だけで解決しようとせず、プログラミング言語のコードと組み合わせたりすることが重要です。
- パフォーマンス問題 (ReDoS): 特定のパターンと入力文字列の組み合わせにより、正規表現エンジンが非常に長い時間(指数関数的に)処理に時間がかかってしまう脆弱性(ReDoS – Regular Expression Denial of Service)が存在します。特に、繰り返しの中に繰り返しがあり、かつそのパターンが曖昧な場合(例:
(a+)+や(.*?)*のようなパターンで、対象文字列がすべてaのような場合など)に発生しやすいです。外部からの入力に対して正規表現を使う場合は、信頼できるライブラリを使う、複雑すぎるパターンは避ける、タイムアウトを設定するなどの対策が必要です。 - 厳密さと実用性のバランス: メールアドレスの例のように、仕様を厳密にすべて正規表現で表現しようとすると、パターンが極端に複雑になり、メンテナンスが不可能になったりReDoSのリスクを高めたりすることがあります。実用上は、ある程度のパターンにマッチすればOKとするか、正規表現で大まかに絞り込んだ後、プログラムコードで詳細なチェックを行うなど、他の手法と組み合わせることも検討しましょう。
- 方言と互換性: 前述のように、正規表現の記法や機能はツールや言語によって微妙に異なります。特に高度な機能(先読み・後読み、 Unicodeプロパティ、再帰パターンなど)を使う場合は、対象となる環境のドキュメントをよく確認する必要があります。
これらの注意点を頭に入れておけば、正規表現をより安全に、効果的に利用できます。
さらに学ぶためのリソース
この記事では正規表現の基本的な要素を網羅しましたが、正規表現の世界はさらに奥深く、様々な応用や高度な機能があります。さらに学びたい場合は、以下のリソースを参考にしてみてください。
- 書籍: 正規表現に特化した解説書が複数出版されています。体系的に学ぶのに適しています。
- オンラインチュートリアル: Web上には多くの正規表現入門サイトやチュートリアルがあります。インタラクティブに学べるものもあります。
- オンラインテスター/デバッガー: 前述の regex101.com や regexr.com などのツールは、パターンを試しながら学ぶのに最適です。リファレンス機能も充実しています。
- 公式ドキュメント: 使用しているプログラミング言語やツールの正規表現に関する公式ドキュメントは、最も正確な情報源です。利用できる機能や方言を確認するのに役立ちます。
正規表現は、実際に手を動かしてパターンを書いてみることが一番の習得方法です。簡単な例から始め、少しずつ複雑な問題に挑戦してみてください。
まとめ
正規表現は、文字列処理において非常に強力で便利なツールです。一見すると難しそうに見える記号の羅列も、一つ一つの意味を理解すれば、決して恐れることはありません。
本記事では、正規表現の以下の基本的な要素を解説しました。
- リテラル文字: その文字そのものにマッチ
- メタ文字:
.: 任意の一文字*,+,?,{}: 量指定子(繰り返しの回数)\: エスケープ文字^,$: 行頭・行末|: OR条件
- 文字クラス:
[...]: 指定した文字集合のいずれか一文字[^...]: 指定した文字集合以外の文字のいずれか一文字\d,\w,\sなどのショートハンド
- グループ化:
()(キャプチャあり)、(?:)(キャプチャなし) - 後方参照:
\1,\2など(キャプチャした内容の再利用) - 先読み・後読み:
(?=),(?!,(?<=),(?<!(マッチ位置の条件指定) - フラグ:
i,g,m,sなど(マッチングの振る舞い変更) - 貪欲マッチと非貪欲マッチ:
*,+などと*?,+?などの違い
これらの要素を組み合わせることで、テキストデータの検索、置換、抽出、バリデーションなど、様々なタスクを効率的にこなすことができます。
正規表現は、一度習得すれば、プログラミング、データ分析、システム管理など、幅広い分野で役立つ一生もののスキルとなります。最初は戸惑うこともあるかもしれませんが、諦めずに練習を重ねてみてください。オンラインツールなどを活用しながら、少しずつステップアップしていきましょう。
正規表現の世界は広大ですが、基本をしっかりと押さえれば、その強力なパワーをあなたの武器にすることができます。ぜひ、今日から正規表現を使ってみてください!