Menu
日本語

JavaScript正規表現入門:test・match・replaceの使い方

JavaScriptの正規表現をサンプル付きで解説。リテラルとRegExpコンストラクタの書き方、test・match・replaceの違い、フラグ、キャプチャグループまで一気に整理します。

正規表現は文字列のパターンを表現するもの

JavaScriptの正規表現は、文字列の「かたち」を記述するためのものです。たとえば「数字4桁」「単語の後にカンマ」「メールアドレスっぽい文字列」といった具合ですね。JavaScriptで正規表現を作る方法は、大きく分けて2つあります。

index.js
Output
Click Run to see the output here.

よく使うのはスラッシュで挟むリテラル記法で、フラグは閉じスラッシュの後ろに書きます。パターン自体をユーザー入力や変数から動的に組み立てたいときだけ、new RegExp(...) の出番です。

末尾の iフラグ と呼ばれるもので、大文字・小文字を区別しないという意味です。フラグについては後ほど詳しく説明します。

test: マッチするかどうかを判定する

正規表現に対して一番シンプルに聞けるのは「この文字列にマッチする部分があるか?」ということ。これを調べるのが test です。

index.js
Output
Click Run to see the output here.

\d は「任意の数字」を表します。testtruefalse しか返しません。入力チェックや配列のフィルタリングのように、マッチしたかどうかだけ知りたい場面ではまさにうってつけです。

match: マッチした文字列を取り出す

マッチした文字列自体が欲しいときは、文字列側の match メソッドを使います。

index.js
Output
Click Run to see the output here.

gフラグを付けない場合、matchは最初にマッチした結果に加えてindexinputといったメタ情報を含む配列を返します。一方、gフラグを付けると、マッチしたすべての文字列を普通の配列として返します。ただし、何もマッチしなかったときは空配列ではなくnullが返ってくるので、その点は忘れずにガードしておきましょう:

index.js
Output
Click Run to see the output here.

フラグでマッチの挙動を変える

フラグは閉じスラッシュの後ろに付けて、マッチの振る舞いを調整します。よく使うものはこのあたり:

  • g — グローバル。最初の1件だけでなく、すべてのマッチを探します。
  • i — 大文字・小文字を区別しません。
  • m — マルチラインモード。^$ が文字列全体ではなく、各行の先頭・末尾にマッチするようになります。
  • s — dotall。. が改行文字にもマッチするようになります。
  • u — Unicode 対応。多くの絵文字や非 ASCII 文字のパターンで必要になります。
index.js
Output
Click Run to see the output here.

m フラグがない場合、^ は文字列全体の先頭にしかマッチしません。m を付けると各行の先頭にアンカーされるので、RosesViolets の両方が拾われます。

文字クラスと量指定子

ほとんどのパターンはこれらの部品で組み立てます。

  • \d は数字、\w は単語構成文字(英数字とアンダースコア)、\s は空白文字。
  • [abc] は a、b、c のいずれか。[^abc] はそれ 以外[a-z] は範囲指定。
  • . は改行を除く任意の 1 文字。
  • * は 0 回以上、+ は 1 回以上、? は 0 回または 1 回。
  • {3} はちょうど 3 回、{2,5} は 2〜5 回、{2,} は 2 回以上。
  • ^ は先頭、$ は末尾。

組み合わせるとこんな感じです。

index.js
Output
Click Run to see the output here.

\b は単語境界を表すメタ文字で、単語構成文字と非単語構成文字の境目にある「見えない区切り」のことです。単語単位でマッチさせたいときに重宝します。

キャプチャグループ:マッチした一部を取り出す

丸括弧で囲むと グループ ができ、そこにマッチした部分が記憶されます。execmatch は、マッチ全体と一緒にこのキャプチャ結果も返してくれます。

index.js
Output
Click Run to see the output here.

インデックス 0 にはマッチ全体が入り、その後ろに各グループが順番に並びます。ただ、グループが3つ以上になると番号で数えるのが途端に面倒になるので、名前を付けてしまいましょう。

index.js
Output
Click Run to see the output here.

名前付きキャプチャグループを使うと、呼び出し側のコードが読みやすくなりますし、パターン内の順序を入れ替えても壊れません。

replace:マッチした部分を書き換える

replace はパターンと置換内容を受け取ります。置換内容には文字列だけでなく関数も渡せます。

index.js
Output
Click Run to see the output here.

g フラグを付けない場合、最初にマッチした箇所しか置換されません。フラグを付け忘れて、2つ目のメールアドレスがまだ変なままだ…と悩むのはよくあるハマりどころです。

置換文字列では後方参照が使えます。$1$2 などはキャプチャグループを、$<name> は名前付きキャプチャグループを参照します。

index.js
Output
Click Run to see the output here.

単純な置換で済まない場合は、関数を渡しましょう。マッチした文字列とキャプチャが引数として渡ってきます。

index.js
Output
Click Run to see the output here.

アンダースコアはマッチ全体(今回は使わない部分)で、n が1つ目のキャプチャグループです。この「正規表現+置換関数」の組み合わせは、実務でのテキスト加工のほとんどをこなせる万能パターンです。

matchAll:すべてのマッチをキャプチャグループごと取得する

String.prototype.matchAll は、各マッチを キャプチャグループごと イテレータで返してくれます。g フラグ付きの match では実現できない動きです。

index.js
Output
Click Run to see the output here.

matchAll を使うときは g フラグが必須です。付け忘れると TypeError になるので注意してください。イテレーションではなくインデックスアクセスしたい場合は、[...text.matchAll(email)] のようにスプレッドして配列にしておくと便利です。

特殊文字のエスケープ

. * + ? ( ) [ ] { } | \ ^ $ といった文字は、正規表現の中では特別な意味を持ちます。これらをそのままの文字として一致させたいときは、バックスラッシュでエスケープします。

index.js
Output
Click Run to see the output here.

エスケープしていない方は examplexcom にもマッチしてしまいます。. は「任意の 1 文字」を表すからです。この手のバグは意外と多く、しかもエラーが出ないので気付きにくい。正規表現が想定より広くマッチしているときは、まずエスケープ漏れの . を疑ってみてください。

ユーザー入力からパターンを組み立てる場合は、必ずエスケープが必要です。そうしないと、正規表現の構文を差し込まれてしまいます。

index.js
Output
Click Run to see the output here.

置換文字列の中で使う $& は「マッチした文字列全体」を表すショートハンドです。

先読み(lookahead)と後読み(lookbehind)

「ある文字列の直後(または直前)にあるときだけマッチさせたいけれど、その部分自体はマッチに含めたくない」という場面ってありますよね。そんなときに活躍するのが lookaround(先読み・後読み)です。

index.js
Output
Click Run to see the output here.
  • (?= ...) は肯定先読み。「〜が後ろに続く」という意味です。
  • (?<= ...) は肯定後読み。「〜が前にある」という意味です。
  • (?! ...)(?<! ...) はそれぞれの否定バージョンです。

先読み・後読みは文字を消費しない(マッチ位置を進めない)ので、「覗き見た」部分はパターンの次のステップでもそのまま使えます。

メール検証の正規表現について一言

「メールアドレスをバリデーションする正規表現をください」というのは本当によくある質問です。正直に言うと、やめておいた方がいいです。実際のメールアドレスの文法はかなり複雑で、読める長さに収まる正規表現はどこかしら間違っています。フォームでの簡易チェック程度なら、現実的なパターンで十分です。

index.js
Output
Click Run to see the output here.

こう読んでください。「空白でも @ でもない文字が続き、@ が来て、また同じような文字が続き、ドットが来て、さらに同じような文字が続く」。RFC 5322 を厳密に満たすわけではありませんが、明らかなタイポはこれで十分弾けます。本当に検証したいなら、確認メールを送りましょう。

よくある落とし穴

頭に入れておきたいハマりどころをいくつか挙げておきます。

  • replacematchAllg フラグを忘れる。 最初の1件しかマッチしなかったり、TypeError になったりします。
  • グローバル正規表現の lastIndex が状態を持つ問題。 gy フラグ付きの正規表現は、testexec の呼び出しをまたいで前回の位置を覚えています。関係のない文字列に同じ正規表現を使い回すのは NG。毎回新しく作るか、matchAll を使いましょう。
  • 動的に組み立てたパターンでドットやスラッシュをエスケープし忘れる。 ユーザー入力を new RegExp(...) に突っ込む前に、必ずエスケープしてください。
  • 壊滅的バックトラッキング(catastrophic backtracking)。 (a+)+ のようなネストした量指定子は、意地悪な入力でタブを丸ごとフリーズさせることがあります。正規表現が妙に遅いと感じたら、まずはシンプルにリファクタしましょう。

次回: 日付と時刻

正規表現はテキストの「形」を扱う道具ですが、実データにはパースやフォーマット、計算が必要なタイムスタンプも付いてきます。次のページでは DateIntl.DateTimeFormat、そしてタイムゾーン由来のバグを寄せつけないための考え方を解説します。

よくある質問

JavaScriptで正規表現を作るには?

書き方は2通りあります。スラッシュで囲むリテラル記法 /hello/i と、文字列から生成するコンストラクタ new RegExp('hello', 'i') です。パターンが固定ならリテラル、変数からパターンを組み立てるなど実行時に決まるならコンストラクタ、と使い分けるのが基本です。

test、match、execの違いは?

regex.test(str) は真偽値を返すので、マッチするかどうかだけ知りたいときに一番速いです。str.match(regex) はマッチ結果の配列(なければ null)を返します。regex.exec(str) は1件ずつマッチを返し、キャプチャグループも取れます。g フラグを付けると lastIndex で位置を覚えているので、ループで順番に取り出せます。

正規表現で全件置換するには?

g フラグを付けて str.replace(/foo/g, 'bar') とします。g なしだと最初の1件しか置換されません。str.replaceAll(/foo/g, 'bar') も使えますが、replaceAll に正規表現を渡す場合は g フラグが必須で、付け忘れるとエラーになります。

キャプチャグループって何?

パターン中の括弧がキャプチャグループで、マッチした部分を覚えてくれます。たとえば /(\d{4})-(\d{2})/.exec('2024-11') の結果は、インデックス1が '2024'、インデックス2が '11' になります。(?<year>\d{4}) のように名前付きにすれば、match.groups.year で取り出せて可読性も上がります。

Coddyでコードを学ぼう

始める