正規表現入門 — 基本メタ文字から実践パターンまで
正規表現とは
正規表現(Regular Expression、略称: regex)は、文字列のパターンを記述するための特殊な記法です。テキストの検索、置換、バリデーションなど、文字列処理が必要なあらゆる場面で活用されます。
ほぼ全てのプログラミング言語が正規表現をサポートしており、一度習得すればJavaScript、Python、Java、PHP、Goなど、言語を問わず応用できます。
正規表現の歴史
正規表現の起源は1950年代にさかのぼります。数学者・計算機科学者の Stephen Kleene が、有限オートマトン理論の一部として正規言語を記述するための表記法を考案しました。
1960年代、Ken Thompson(後にC言語とUnixを生み出した人物)はKleeneの理論をプログラムに応用し、テキストエディタ ed と検索コマンド grep(Global Regular Expression Print)に組み込みました。これにより正規表現は実用ツールとして広く普及します。
1980年代には POSIX(Portable Operating System Interface)が正規表現の標準仕様を策定し、BRE(Basic Regular Expressions)と ERE(Extended Regular Expressions)の2種類が定義されました。
その後、Larry Wall が開発した Perl 言語が正規表現の表現力を大幅に拡張しました。名前付きキャプチャグループ、先読み・後読み、非貪欲量指定子など、今日の正規表現で当たり前のように使われる機能の多くは Perl 由来です。この拡張仕様は PCRE(Perl Compatible Regular Expressions) と呼ばれ、PHP、Python、Rubyなど多くの言語がこれを採用しています。
基本のメタ文字
正規表現は通常の文字とメタ文字(特殊な意味を持つ文字)の組み合わせで構成されます。まず、基本的なメタ文字を押さえましょう。
文字クラス
| メタ文字 | 意味 | 例 | マッチする文字列 |
|---|---|---|---|
. |
任意の1文字(改行以外) | a.c |
abc, a1c, a-c |
\d |
数字(0-9) | \d{3} |
123, 456 |
\D |
数字以外 | \D+ |
abc, --- |
\w |
英数字とアンダースコア | \w+ |
hello_123 |
\W |
\w 以外 |
\W |
@, #, |
\s |
空白文字 | \s+ |
, \t, \n |
\S |
空白以外 | \S+ |
hello |
アンカー
アンカーは文字そのものではなく、位置にマッチします。
| メタ文字 | 意味 | 例 | 説明 |
|---|---|---|---|
^ |
行頭 | ^Hello |
行頭の「Hello」にマッチ |
$ |
行末 | end$ |
行末の「end」にマッチ |
\b |
単語境界 | \bcat\b |
「cat」に一致、「catch」には不一致 |
文字セット
角括弧 [] で独自の文字クラスを定義できます。
[abc] # a, b, c のいずれか
[a-z] # 英小文字
[A-Za-z] # 英字(大小文字)
[0-9] # 数字(\d と同等)
[^0-9] # 数字以外(^ は否定)
[a-zA-Z0-9_] # 英数字とアンダースコア(\w と同等)
特殊文字のエスケープ
. * + ? ^ $ { } [ ] ( ) | \ はメタ文字であるため、これらをリテラル(そのままの文字)として検索する場合はバックスラッシュ \ でエスケープする必要があります。
# ピリオドをリテラルとして検索
3\.14 # "3.14" にマッチ("3X14" にはマッチしない)
# 括弧をリテラルとして検索
\(hello\) # "(hello)" にマッチ
量指定子(Quantifiers)
量指定子は、直前の要素が何回繰り返されるかを指定します。
| 量指定子 | 意味 | 例 | マッチする文字列 |
|---|---|---|---|
* |
0回以上 | ab*c |
ac, abc, abbc |
+ |
1回以上 | ab+c |
abc, abbc |
? |
0回または1回 | colou?r |
color, colour |
{n} |
ちょうどn回 | \d{4} |
2026 |
{n,} |
n回以上 | \d{2,} |
12, 123, 1234 |
{n,m} |
n回以上m回以下 | \d{2,4} |
12, 123, 1234 |
貪欲マッチと最短マッチ
デフォルトでは量指定子は「貪欲(greedy)」で、可能な限り多くの文字にマッチします。末尾に ? を付けると「最短(lazy)」マッチに変わります。
# 入力: <div>hello</div>
<.*> # 貪欲: <div>hello</div> 全体にマッチ
<.*?> # 最短: <div> にマッチ
HTMLタグの抽出やクォートされた文字列の取得では、最短マッチが有用です。
典型的な使い分け:
- 貪欲マッチ — ファイルパスの末尾(拡張子以外の部分)を取得するなど、できる限り多く取りたい場合
- 最短マッチ — HTMLタグ内のテキスト、引用符で囲まれた文字列など、最初に閉じるデリミタまでで止めたい場合
グループ化とキャプチャ
丸括弧 () でパターンをグループ化し、マッチした部分をキャプチャ(抽出)できます。
基本的なグループ化
(abc)+ # 「abc」の1回以上の繰り返し
(red|blue) # 「red」または「blue」
キャプチャグループの活用
const pattern = /(\d{4})-(\d{2})-(\d{2})/;
const match = "2026-03-08".match(pattern);
console.log(match[1]); // "2026" (年)
console.log(match[2]); // "03" (月)
console.log(match[3]); // "08" (日)
名前付きキャプチャグループ
(?<name>...) の構文で、グループに名前を付けられます。可読性が大幅に向上します。
const pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = "2026-03-08".match(pattern);
console.log(match.groups.year); // "2026"
console.log(match.groups.month); // "03"
console.log(match.groups.day); // "08"
後方参照(Backreference)
キャプチャグループは \1, \2 ... という「後方参照」を使って、同じ文字列が繰り返されるパターンを検出できます。
# 連続する重複単語を検出(例: "the the")
\b(\w+)\s+\1\b
# 同じ引用符で開始・終了する文字列にマッチ
(['"]).*?\1
# "hello" や 'world' にマッチ(開き引用符と閉じ引用符が同じ)
JavaScriptの replace でも後方参照を活用できます。
// 日付フォーマットを YYYY-MM-DD から DD/MM/YYYY に変換
const result = "2026-03-08".replace(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
"$<day>/$<month>/$<year>"
);
console.log(result); // "08/03/2026"
非キャプチャグループ
マッチ結果を保存する必要がない場合、(?:...) を使うと非キャプチャグループになります。パフォーマンスの面でわずかに有利です。
(?:http|https):// # プロトコル部分をグループ化するが、キャプチャはしない
先読みと後読み(Lookahead / Lookbehind)
先読みと後読みは、パターンの前後にある文字列を条件として指定しますが、マッチ結果自体には含みません。
| 構文 | 名称 | 説明 |
|---|---|---|
(?=...) |
肯定先読み | 直後に指定パターンが続く位置 |
(?!...) |
否定先読み | 直後に指定パターンが続かない位置 |
(?<=...) |
肯定後読み | 直前に指定パターンがある位置 |
(?<!...) |
否定後読み | 直前に指定パターンがない位置 |
# 「円」が後に続く数値にマッチ(「円」自体はマッチに含まない)
\d+(?=円)
# 入力: "100円 200ドル 300円"
# マッチ: "100", "300"
# 「$」の後に続く数値にマッチ
(?<=\$)\d+
# 入力: "$100 200 $300"
# マッチ: "100", "300"
先読みと後読みが特に威力を発揮するのは、「特定の文字列を含むが、その文字列自体は置換したくない」場面です。
// "color" を "colour" に置換するが、"colorful" の中の "color" は除外する
const text = "This color is colorful.";
const result = text.replace(/color(?!ful)/g, "colour");
console.log(result); // "This colour is colorful."
実践的なパターン例
メールアドレスの検証
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
このパターンは一般的なメールアドレスの形式を検証します。ただし、RFC 5321に完全に準拠した検証はより複雑になります。実務では、形式チェックは簡易的に行い、確認メールの送信で有効性を検証するアプローチが推奨されます。
電話番号(日本の形式)
^0\d{1,4}-?\d{1,4}-?\d{3,4}$
ハイフンあり・なしの両方に対応しています。より厳密にする場合は、市外局番のパターンを個別に定義する必要があります。
URLの検証
^https?:\/\/[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z]{2,})+([\/\w.-]*)*\/?$
郵便番号(日本)
^\d{3}-?\d{4}$
「123-4567」と「1234567」の両方にマッチします。
IPアドレス(IPv4)
^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$
各オクテットが 0〜255 の範囲内であることを厳密に検証します。25[0-5] は 250〜255、2[0-4]\d は 200〜249、[01]?\d\d? は 0〜199 にそれぞれ対応しています。
日付(YYYY-MM-DD 形式)
^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$
月は 01〜12、日は 01〜31 の範囲に制限しています。ただし、2月の日数制限や閏年の考慮は正規表現だけでは難しいため、日付のパースはライブラリに任せるのが現実的です。
パスワード強度チェック
8文字以上で、英大文字・英小文字・数字をそれぞれ1つ以上含むことを検証します。
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}$
このパターンでは肯定先読みを3つ使い、各文字種の存在を個別にチェックしています。
言語ごとの正規表現エンジンの違い
正規表現の基本構文は多くの言語で共通していますが、エンジンの仕様や独自拡張に注意が必要です。
| 機能 | JavaScript | Python (re) |
Java | PCRE(PHP等) |
|---|---|---|---|---|
| 名前付きキャプチャ | (?<name>...) |
(?P<name>...) |
(?<name>...) |
(?<name>...) |
| 後読み(可変長) | 非対応(ES2018で固定長のみ) | 対応 | 対応 | PCRE2で対応 |
Unicode プロパティ \p{L} |
u フラグで対応 |
対応 | 対応 | u モードで対応 |
所有格量指定子 a++ |
非対応 | 非対応 | 対応 | 対応 |
インライン修飾子 (?i) |
非対応(フラグ方式) | 対応 | 対応 | 対応 |
| グローバル検索 | g フラグ |
re.findall() |
Matcher.find() ループ |
preg_match_all() |
主な注意点:
- JavaScript はフラグ(
g,i,m,s,u,v)をパターンの外に付ける方式で、他言語とは異なります。ES2018 から後読みを限定的にサポートしましたが、可変長の後読みはまだ非対応です。 - Python の名前付きキャプチャは
(?P<name>...)という独自記法で、(?P=name)で後方参照します。 - Java の
String.matches()はパターンが文字列全体にマッチするかを検証するため、^...$のアンカーが不要です(他言語のfullmatchに相当)。
パターンを他の言語に移植する際は、正規表現チートシート で言語別の構文差異を確認することをお勧めします。
正規表現のパフォーマンス
正規表現は強力ですが、記述によってはパフォーマンスの問題を引き起こすことがあります。
カタストロフィック・バックトラッキング
正規表現エンジンの多くは「バックトラッキング(後退探索)」方式を採用しています。ある位置でマッチに失敗すると、エンジンは一歩戻って別の経路を試みます。通常これは高速ですが、パターンの設計次第で指数的に処理時間が増加する「カタストロフィック・バックトラッキング(破滅的後退探索)」が発生することがあります。
# 危険なパターン例: ネストした量指定子
(a+)+b
# 入力 "aaaaaaaaaaaac" のとき、エンジンは指数的に多くの
# 組み合わせを試し、タイムアウトやフリーズを引き起こす
ReDoS(Regular Expression Denial of Service)として知られるこの問題は、Webアプリケーションのセキュリティ脆弱性にもなり得ます。実際、CloudflareやStackOverflowが過去にこの問題で障害を起こした事例があります。
回避策:
# NG: ネストした量指定子
(a+)+
# OK: アトミックグループ(PCREで利用可能)
(?>a+)+
# OK: 所有格量指定子(JavaやPCREで利用可能)
a++
JavaScriptなど所有格量指定子やアトミックグループが使えない環境では、パターンを見直して重複する探索経路を排除することが重要です。
パフォーマンス改善のポイント
- 不必要なキャプチャを避ける: キャプチャ不要な箇所は
(?:...)を使う - 文字クラスを活用する:
[abc]はa|b|cより効率的 - アンカーを活用する:
^や$で早期失敗を促す - 具体的なパターンを書く:
.+より\w+や[a-z]+の方が速い - 複雑なパターンを分割する: 1つの正規表現で全てを処理しようとせず、複数のステップに分割する
動作を可視化するには 正規表現テスター を活用してください。ステップ数の確認でボトルネックを発見できます。
正規表現フラグ
多くの言語では、正規表現にフラグ(オプション)を付けて動作を変更できます。
| フラグ | JavaScript | Python | 説明 |
|---|---|---|---|
| 大小文字無視 | i |
re.IGNORECASE |
[a-z] が [A-Za-z] と同等に |
| 複数行モード | m |
re.MULTILINE |
^ $ が各行の先頭・末尾に対応 |
| ドット全一致 | s |
re.DOTALL |
. が改行文字にもマッチ |
| グローバル | g |
— | 全マッチを返す(findall 相当) |
| Unicode | u |
— | \p{L} 等のUnicodeプロパティを有効化 |
// i フラグで大小文字を無視
const pattern = /hello/i;
pattern.test("Hello World"); // true
pattern.test("HELLO"); // true
// m フラグで複数行の行頭にマッチ
const multiline = /^start/m;
"line1\nstart here".match(multiline); // ["start"]
まとめと学習のすすめ方
正規表現はテキスト処理における万能ツールです。メタ文字、量指定子、グループ化、先読みの基本を押さえれば、多くの実務的な課題に対応できます。
効率的な習得ステップ:
- 基本メタ文字(
.,\d,\w,\s)と量指定子(*,+,?)から始める - キャプチャグループと文字クラス
[]を使いこなす - アンカー(
^,$,\b)でパターンの精度を上げる - 先読み・後読みで高度な条件指定に挑戦する
- パフォーマンスを意識してパターンを最適化する
最初は複雑に感じるかもしれませんが、よく使うパターンから少しずつ覚えていけば、確実に習得できます。パターンの動作をリアルタイムに確認しながら学習を進めるには 正規表現テスター が効果的です。また、メタ文字や構文を一覧で確認したいときは 正規表現チートシート を参考にしてください。
参考文献
- ECMA-262 — Regular Expressions(JavaScript 正規表現の仕様)
- Python
reモジュール公式ドキュメント - PCRE2 プロジェクト
- Mastering Regular Expressions — Jeffrey E.F. Friedl 著(O'Reilly、正規表現の定番書)
- RFC 5321 — Simple Mail Transfer Protocol(メールアドレス仕様)
