正規表現の作り方|検索・置換を壊さない小さな試し方
JavaScriptの正規表現を、普通の文字列から少しずつ組み立て、置換前に境界値と期待結果を確かめる手順を解説します。
正規表現(=文字の並び方をパターンで指定する検索の書き方)は、検索や置換を短く書ける便利な道具です。その一方で、複雑なパターンは「どこまで一致するのか」が分かりにくくなります。安全に作るコツは3つです。普通の文字列から始めること。記号を一つずつ足すこと。一致してほしくない例も一緒に試すことです。
この記事ではJavaScriptのRegExpを使います。JavaScriptの正規表現が「パターン」と「フラグ(=検索の設定)」の組で動くことは、言語の正式な仕様であるECMAScript® 2026 Language Specificationが定めています。エディタや別の言語では使える構文が異なるため、移植するときは使う道具の公式資料でその時点の対応状況を確認してください。
正規表現を書く前に範囲と合格条件を決める
パターンを書き始める前に、メモへ次の5つを書き出します。
- 対象例: 一致してほしい実例(例:
ORDER-1234) - 対象外例: 一致してほしくない実例(例:
ORDER-99999、order-1234) - 期待する一致件数: 「このファイルなら3件のはず」
- どこまでやるか: 検索だけで止めるか、置換まで進むか
- 触ってよい範囲: 選択した行だけか、1ファイルか、フォルダ全体か
最初の合格条件は「一括置換が完成すること」ではありません。「読み取りだけで、期待した候補が期待した件数だけ並ぶこと」です。VS Codeなら検索を選択範囲に限定でき、複数ファイルの置換では実行前に差分(=変更前後の比較)を確かめられます(Basic editing)。
AIに正規表現を作ってもらう場合も、この5つをそのまま渡します。渡す例文に本番の個人情報や秘密のデータを貼らないでください。形だけ同じ架空のデータで十分です。依頼の分け方はCodexへの依頼テンプレートも参考になります。
普通の文字列から記号を一つずつ足す
MDNの解説にあるとおり、正規表現は/ORDER-/のようなリテラルと、new RegExp("ORDER-")のようなコンストラクターの2通りで作れます(Regular expressions)。固定のパターンはリテラルで書くのが簡単です。
入力例の中で変わらない文字から探します。注文番号がORDER-1234なら、まずORDER-だけを検索します。
const text = "受付: ORDER-1234 / 再送: ORDER-9876";
const pattern = /ORDER-/g;
console.log([...text.matchAll(pattern)].map((match) => match.index));
// [4, 21]
gフラグは「全部の一致を探す」指定です。一致の位置と件数も見ると、想定外の一致に早く気付けます。
後半が4桁の半角数字なら[0-9]{4}を足します。[0-9]は0から9のどれか1文字、{4}は直前の要素を4回繰り返す指定です。
const pattern = /ORDER-[0-9]{4}/g;
const sample = "ORDER-1234 ORDER-12A4 ORDER-99999";
console.log(sample.match(pattern));
// ["ORDER-1234", "ORDER-9999"]
ここで対象外例が効きます。ORDER-99999の先頭4桁にも一致してしまいました。記号は一つ足すたびに実行し、「新しく何が一致するようになったか」を見比べます。最初から複雑な一行をAIに作らせないのも同じ理由です。条件を小さく分ける考え方はAIへの指示が長すぎるときの整理法にもつながります。
回数の指定(量指定子)は、?が0〜1回、*が0回以上、+が1回以上、{4}が4回ちょうど、{2,4}が2〜4回です(Quantifier)。桁数が決まっているなら、{4}のように範囲を明示します。
よく使う指定の早見表
| 指定 | 意味 | 使いどころ | 注意点 |
|---|---|---|---|
[0-9] | 半角数字1文字 | IDや固定形式の桁 | 全角数字は対象外 |
{4} | 直前の要素を4回 | 4桁固定 | 5桁の先頭4桁にも一致し得る |
{2,4} | 直前の要素を2〜4回 | 桁数に幅がある入力 | 上限と下限を仕様で決める |
^ / $ | 入力の先頭 / 末尾 | 文字列全体の形式確認 | mフラグ使用時は各行の境界になる |
g | 全ての一致を探す | 一覧取得や一括置換 | 同じ正規表現でtest()を繰り返すとlastIndexが変わる |
i | 大文字・小文字を区別しない | 表記ゆれを許す検索 | 区別が必要なIDには付けない |
境界・貪欲一致・テストで一致範囲を閉じる
^と$で入力全体を囲む
入力全体を「ORDER-+4桁」だけに限定するなら、先頭の^と末尾の$で囲みます。^と$は文字を消費せず、入力の境界だけを確認する指定です(Input boundary assertion)。
const orderPattern = /^ORDER-[0-9]{4}$/;
console.log(orderPattern.test("ORDER-1234")); // true
console.log(orderPattern.test("ORDER-99999")); // false
注意点が1つあります。mフラグを付けると、^と$は文字列全体だけでなく各行の先頭・末尾にも一致します。パターンだけでなく、フラグも一緒に確認してください。
貪欲一致を疑う
量指定子は既定で、できるだけ長い範囲を取ろうとします。これが貪欲一致です。
const text = "<strong>重要</strong><em>補足</em>";
console.log(text.match(/<.*>/g));
// ["<strong>重要</strong><em>補足</em>"]
console.log(text.match(/<.*?>/g));
// ["<strong>", "</strong>", "<em>", "</em>"]
*?のように?を足すと短い一致を優先します(Quantifier)。ただし「非貪欲にすれば安全」とは限りません。できれば<[^>]*>のように「>以外の文字」と許す範囲そのものを書く方が、意図が読みやすくなります。なお、これは貪欲性を知るための小さな例です。属性や入れ子を含むHTML全体の解析には、DOMパーサー(=HTMLを構造として読む道具)を検討してください。
パターンを単純に保つ理由はもう1つあります。V8(ChromeやNode.jsのJavaScriptエンジン)の公式ブログは、/(a*)*b/のような入れ子の量指定子を含む一部のパターンで、実行時間が入力の長さに対して爆発的に増える場合があると説明しています(An additional non-backtracking RegExp engine)。繰り返しの入れ子は避けて単純に書き、テストには「一致しない長めの入力」も混ぜておくと安心です。
期待結果を先に固定してテストする
正しい例だけでは、広すぎるパターンを見抜けません。正常、1桁短い、1桁長い、空文字、前後の空白、大文字小文字を並べ、期待するtrue/falseを先に決めます。
const pattern = /^ORDER-[0-9]{4}$/;
const cases = [
["ORDER-0000", true],
["ORDER-9999", true],
["ORDER-999", false],
["ORDER-10000", false],
["order-1234", false],
[" ORDER-1234 ", false],
["", false],
];
for (const [value, expected] of cases) {
const actual = pattern.test(value);
console.assert(actual === expected, { value, expected, actual });
}
test()は一致すればtrue、しなければfalseを返します(RegExp.prototype.test())。ここに罠が1つあります。gまたはyフラグ付きの同じRegExpを使い回すと、test()は前回の続き(lastIndexが示す位置)から検索を始めます。一致するとlastIndexは一致の終わりへ進み、一致しないと0へ戻ります(RegExp: lastIndex)。単純な形式チェックではgを外すか、テストごとに正規表現を作り直してください。
iフラグで大文字と小文字を同一視するかは仕様で決めます。変更のたびにテストを再実行し、分担するときは期待結果も一緒に共有します。実データに近い対象での使い方はログから正規表現で必要箇所を抜き出す実例で確認できます。
置換は一致一覧と差分を見てから
JavaScriptのreplace()の決まり
replace()は元の文字列を変更せず、置換後の新しい文字列を返します。検索側が普通の文字列なら最初の1件だけを置換し、全部を置換するには正規表現にgフラグが必要です(String.prototype.replace())。
丸括弧()で囲んだ部分はキャプチャグループ(=あとで取り出せる部分)になり、置換文字列から$1のように参照できます。
const input = "公開予定日は2026-07-23です";
const datePattern = /([0-9]{4})-([0-9]{2})-([0-9]{2})/;
const output = input.replace(datePattern, "$1/$2/$3");
console.log(output);
// 公開予定日は2026/07/23です
$1や$<名前>が使えるのは、検索側が正規表現で、対応するグループがある場合だけです。もう1つ注意があります。2026-99-99も「4桁-2桁-2桁」の形なら一致します。形式の確認と、日付として正しいかの確認は分けてください。
エディタでの安全な手順
エディタの置換ボタンは、replace()と違ってファイルそのものを書き換えます。次の順で進めると、いつでも戻れます。
- 対象ファイルやレコードを列挙する
- 置換せず、一致件数と一致文字列を表示する
- 対象例と対象外例をテストする
- コピー(または選択範囲)で1件だけ置換し、差分を確認する
- バックアップと戻し方を確認する
- 対象を限定して実行し、結果件数を照合する
VS Codeは検索を選択範囲へ限定でき、複数ファイルの置換では実行前の変更を差分表示し、1件・1ファイル・全ファイルの単位で置換できます(Basic editing)。一気に全体へかけず、1件→1ファイル→全体の順に広げます。
変動する検索語を組み込むとき
利用者の入力をそのままnew RegExp(入力)へ入れると、.や+が正規表現の記号として解釈されます。新しい実行環境には、文字列を「ただの文字の並び」として安全に埋め込めるRegExp.escape()があります(RegExp.escape())。ただしMDNはこれを2025年5月以降の新しい機能としており、古い環境では動かない可能性があります。使う前に、対象のブラウザーやNode.jsの対応状況をその時点の公式案内で確認してください。またnew RegExp("\\d{4}")のように、JavaScriptの文字列としてのエスケープ(\\)も別に必要です。
パターンが複雑になったら、処理を二段階に分けるか、普通の文字列処理へ戻すのも立派な解決です。
よくある質問
正規表現はどこから作り始めればよいですか
必ず含まれる普通の文字列だけで検索し、結果を見てから文字クラス、回数、境界を一つずつ足します。追加するたびに対象例と対象外例を同時に実行すると、一致範囲が広がった箇所を追いやすくなります。最初から一行で完成させないのがコツです。
^と$を付ければ必ず文字列全体だけに一致しますか
通常は入力の先頭と末尾を表します。ただしmフラグを付けると、^と$は各行の先頭と末尾にも一致します。複数行を一つの入力として検証する場合は、パターンだけでなくフラグを含めて確認してください。
test()の結果が同じ入力でも交互に変わるのはなぜですか
gまたはyフラグ付きの同じ正規表現は、次の検索開始位置をlastIndexに保存するためです。一致するとlastIndexが進み、次のtest()はそこから探し始めます。独立した真偽テストではgを外すか、ケースごとに新しいRegExpを使います。
置換欄の$1がそのまま文字として残るのはなぜですか
$1は、検索側が正規表現で、1番目のキャプチャグループがある場合に使える参照です。検索側が普通の文字列だったり、該当するグループが無かったりすると、期待した参照になりません。検索パターンとグループの数を確認してください。
利用者が入力した検索語をnew RegExp()へそのまま入れてよいですか
そのまま入れないでください。.、+、(などが記号として解釈されます。対応している環境ではRegExp.escape()でリテラル化できますが、新しい機能のため、古い環境での互換性と代替手段はその時点の公式資料で確認してください。
まとめ
安全な正規表現は、短い固定文字列から始め、記号を一つ足すたびに対象例と対象外例を再実行し、置換の前に一致一覧と差分を見る流れで作れます。AIに頼むときも、パターンそのものだけでなく、範囲・期待件数・止める地点・戻し方まで渡します。次にやることは3つです。
- 直したいデータの「対象例・対象外例・期待する一致件数」をメモに書き出し、まず読み取りだけの検索で件数を合わせる
- 記号を一つ足すたびにテストを再実行し、
g付き正規表現の使い回し(lastIndex)に気を付ける - 置換はコピーか選択範囲で1件だけ試し、差分と戻し方を確かめてから1ファイル→全体へ広げる
公式一次情報
確認日: 2026-07-26
- ECMAScript® 2026 Language Specification: RegExpオブジェクト、パターン文法、
lastIndexなどJavaScript正規表現の標準動作を定めた仕様 - Regular expressions - JavaScript | MDN: リテラルと
RegExpコンストラクターの2通りの作り方と、特殊文字・グループ・量指定子の概要 - Quantifier - JavaScript | MDN:
?*+{n}{n,m}の回数指定と、既定は貪欲で?を足すと非貪欲になること - Input boundary assertion - JavaScript | MDN:
^と$は入力の境界を確認し、mフラグでは各行の境界にもなること - RegExp.prototype.test() - JavaScript | MDN:
test()の真偽の返し方と、g/y付きの正規表現が状態を持つこと - RegExp: lastIndex - JavaScript | MDN: 次の検索開始位置
lastIndexが一致・不一致でどう変わるか - String.prototype.replace() - JavaScript | MDN: 元の文字列を変えず新しい文字列を返すこと、
gフラグと$1・$<Name>の決まり - RegExp.escape() - JavaScript | MDN: 入力文字列を正規表現のリテラルとして安全に埋め込む方法と、新しい機能である注意
- Basic editing (Visual Studio Code): 選択範囲に限定した検索と、差分表示つきで段階的に進められる複数ファイル置換
- An additional non-backtracking RegExp engine · V8: 入れ子の量指定子で実行時間が爆発し得る「破滅的バックトラッキング」の説明
続き(結論と実データ)はnoteに置いています
ここでは手順のところまで書きました。実際に出た数字、うまくいかなかった条件、そのまま使える設定ファイルは、 note の記事にまとめてあります。