初心者がミスしやすい正規表現に潜む”落とし穴”
【初心者向けまとめ】
正規表現を使っていて、正しく使っているのに「なぜか機能してくれない」
「思い通りにマッチしてくれない」、そんなことはありませんでしたか?
それは、正規表現に潜む“落とし穴”が原因かもしれません。
この記事では、初心者がつまずきやすいポイントとその対処法をわかりやすく解説します。
よくある落とし穴
1. 貪欲マッチと非貪欲マッチ
.* はできるだけ多くマッチするため、意図以上に広く取ってしまうことがあります。
const text = "<title>Example</title>";
console.log(text.match(/<.*>/)); // ["<title>Example</title>"]
<title> だけ取りたいのに、最初の < から最後の > まで全部取ってしまうのが
貪欲マッチです。
非貪欲にしたい場合は *? を使いましょう。
console.log(text.match(/<.*?>/)); // ["<title>"]
貪欲と非貪欲の違い:
-
*+{n,}→ 貪欲(できるだけ多くマッチ) -
*?+?{n,}?→ 非貪欲(できるだけ少なくマッチ)
2. ドット . は改行にマッチしない
よく「なんで全部にマッチしないの?」と混乱するポイントです。
const text = "Hello\nWorld";
console.log(/Hello.World/.test(text)); // false
改行も含めてマッチしたい場合は s フラグを使いましょう。
console.log(/Hello.World/s.test(text)); // true
s フラグが使えない環境では [\s\S] や [^] で「任意の文字(改行含む)」を
表現できます。
console.log(/Hello[\s\S]World/.test(text)); // true
3. \b(単語境界)は日本語では機能しない
\b は「英数字とそれ以外の境界」を指すため、日本語では期待通り動きません。
console.log(/\b猫\b/.test("私は猫です")); // false
日本語での代替案:
前後の文字を明示的に指定する方法があります。
// 空白や句読点の後の「猫」を検索
console.log(/[\s、。]猫/.test("私は、猫です")); // true
// 文の先頭または空白の後の「猫」
console.log(/(?:^|\s)猫/.test("猫です")); // true
console.log(/(?:^|\s)猫/.test("私は 猫です")); // true
// 日本語文字以外に囲まれた「猫」
console.log(/[^\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF]猫[^\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF]/.test(" 猫 ")); // true
完全な解決策はありませんが、用途に応じて前後の条件を工夫することで対応できます。
4. \d や \w の挙動に注意
JavaScriptの \d と \w の挙動には注意が必要です。
\d について:
- 基本的には半角数字 (0-9) のみにマッチします
- しかし、一部のブラウザや環境では全角数字にもマッチすることがあります
- 確実に半角のみを対象にしたい場合は
[0-9]を使用してください
// 環境により挙動が異なる可能性がある
console.log(/\d/.test("1")); // 環境によってはtrue
// 確実に半角のみ
console.log(/[0-9]/.test("1")); // false
console.log(/[0-9]/.test("1")); // true
※JavaScript の \d は仕様上「半角数字 (0–9) のみ」にマッチします。
他言語では Unicode の数字にマッチする場合があるため注意してください。
\w について:
- 英数字とアンダースコア
[a-zA-Z0-9_]のみにマッチします - 日本語などの非ASCII文字にはマッチしません
console.log(/\w/.test("あ")); // false
console.log(/\w/.test("a")); // true
console.log(/\w/.test("_")); // true
Unicode対応が必要な場合:
u フラグと \p{} を使うと、より正確なマッチングができます。
// Unicode プロパティを使用
console.log(/\p{Number}/u.test("1")); // true(全角数字)
console.log(/\p{Number}/u.test("1")); // true(半角数字)
console.log(/\p{Script=Hiragana}/u.test("あ")); // true(ひらがな)
※u フラグを付けても \w の対象は拡張されず、ASCIIの英数字と _ のままです。Unicode文字を扱いたい場合は \p{} を使いましょう。
5. 文字クラス [] の中では意味が変わる記号がある
[] の中では以下のように意味が変わります:
-
^→ 先頭にある場合は「否定」 -
-→ 範囲指定 -
\→ エスケープ
/[^0-9]/ // 数字以外
/[a-z]/ // a〜z
意図せず範囲になってしまう例:
文字クラスの範囲指定は「文字コードの順序」で決まるため、見た目の並び順とは異なる結果になることがあります。
/[.-]/ // OK: "." と "-" の2文字
/[.-0]/ // NG: "." から "0" の範囲(. / 0 などが含まれる)
- を文字として使いたい場合:
先頭か末尾に置くか、エスケープします。
/[0-9-]/ // OK: 末尾に配置
/[-0-9]/ // OK: 先頭に配置
/[0-9\-]/ // OK: エスケープ
^ を文字として使いたい場合:
先頭以外に置くか、エスケープします。
/[0-9^]/ // OK: 先頭以外
/[\^0-9]/ // OK: エスケープ
6. エスケープ忘れで意図しない動作になる
.、+、*、?、|、()、[]、{}、^、$ などは正規表現で特別な意味を持つ
メタ文字です。
「その文字そのもの」を検索したい場合は \ でエスケープが必要です。
間違った例:
// . は「任意の1文字」にマッチするため、意図しない結果に
console.log(/example.com/.test("exampleXcom")); // true(本当はfalseにしたい)
console.log(/example.com/.test("example.com")); // true(偶然正しい)
// + は「1回以上の繰り返し」の意味になる
console.log(/1+1/.test("111")); // true(「1が1回以上」+「1」にマッチ)
console.log(/1+1/.test("1+1")); // true(これもマッチしてしまう)
正しい例:
// メタ文字をエスケープすることで、文字そのものを検索
console.log(/example\.com/.test("exampleXcom")); // false
console.log(/example\.com/.test("example.com")); // true
console.log(/1\+1/.test("111")); // false
console.log(/1\+1/.test("1+1")); // true
// その他のよくあるエスケープが必要なケース
console.log(/\$100/.test("$100")); // true($記号)
console.log(/\(注\)/.test("(注)")); // true(丸括弧)
console.log(/\[重要\]/.test("[重要]")); // true(角括弧)
console.log(/1\.5/.test("1.5")); // true(小数点)
console.log(/a\*b/.test("a*b")); // true(アスタリスク)
エスケープが必要な主なメタ文字:
. + * ? | ( ) [ ] { } ^ $ \
これらの文字を検索したい場合は、必ず \ を前に付けましょう。
7. アンカー ^ と $ の誤解
^ と $ は「行の先頭・末尾」にマッチしますが、文字列全体に対して働くとは
限りません。
特に JavaScript の m(複数行)フラグを付けると挙動が変わるため注意が必要です。
const text = "Hello\nWorld";
// m なし(デフォルト): 文字列全体の先頭・末尾
console.log(/^World$/.test(text)); // false
// m あり(複数行モード): 各行の先頭・末尾
console.log(/^World$/m.test(text)); // true
使い分けのポイント:
-
行単位でマッチしたい →
mフラグを付ける -
文字列全体でマッチしたい →
mフラグを付けない
と意識すると混乱しにくくなります。
const multiLine = "apple\nbanana\ncherry";
// 各行の先頭が "b" で始まる行を探す
console.log(/^b/m.test(multiLine)); // true(banana行)
// 文字列全体が "b" で始まるかチェック
console.log(/^b/.test(multiLine)); // false(先頭はapple)
8. グループ化の丸括弧 () とキャプチャの意図しない発生
() はグループ化と同時に「キャプチャ(後で参照できる)」も行います。
しかし、ただのまとまりとして使いたいのにキャプチャされてしまうことがよくあります。
const text = "abc123";
const result = text.match(/abc(123)/);
console.log(result[0]); // "abc123"(マッチ全体)
console.log(result[1]); // "123"(キャプチャされている)
キャプチャが不要な場合は (?:) を使うと安全です。
const result2 = text.match(/abc(?:123)/);
console.log(result2[0]); // "abc123"(マッチ全体)
console.log(result2[1]); // undefined(キャプチャされない)
使い分けのポイント:
-
キャプチャしたい部分 →
() -
キャプチャ不要のグループ →
(?:)
と明確に使い分けることで、意図しない動作を防げます、
特に複雑な正規表現では、この使い分けが重要です。
// 複数のキャプチャグループ
const email = "user@example.com";
const pattern = /([a-z]+)@([a-z]+)\.com/;
const match = email.match(pattern);
console.log(match[1]); // "user"
console.log(match[2]); // "example"
// 非キャプチャグループを混在
const pattern2 = /([a-z]+)@(?:[a-z]+)\.com/;
const match2 = email.match(pattern2);
console.log(match2[1]); // "user"
console.log(match2[2]); // undefined(@以降はキャプチャしない)
まとめ
正規表現は便利ですが、細かな仕様を知らないと意図しない動作を引き起こしがちです。
今回紹介したポイントを押さえておくことで、より正確で安全な正規表現を書くことができるようになります。
特に注意すべきポイント:
- 貪欲マッチ(
.*)は最大限マッチする -
.は改行にマッチしない -
\bは日本語で機能しない -
\dや\wの挙動は環境に注意 - 文字クラス内では
^と-の意味が変わる - メタ文字は必ずエスケープする
-
^$はmフラグで挙動が変わる -
()は自動的にキャプチャする
これらを意識することで、正規表現の「なぜか動かない」を減らすことができます!