3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【完全ガイド】正規表現をゼロから ― 選択・繰り返し・位置・先読み・置換まで一気に

3
Posted at

はじめに

問い合わせ台帳から「わたなべさん」の対応履歴を探したいのに、「辺」だったか「邊」だったか思い出せない。経費データから「2024 年の分」だけ抜きたいのに、金額の 2024 まで引っかかってしまう。ログを読みやすくするために、いらない行を手作業で消し続けている。日付の並び順を直すために、何百行も手打ちしている。

こうした「あと一歩」の作業でつまずいたとき、効いてくるのが正規表現です。正規表現はプログラマだけのものではなく、検索欄に少し書き足すだけで日常業務の手数が減る道具でもあります。

この記事では 選択(どれか) → 繰り返し(何回) → 位置(どこ) → 特殊な文字(タブ・改行・空白・数字) → 先読み → 最短一致 → 置換とキャプチャ → 複数ファイルへの展開 までを1本にまとめました。1つずつは地味な記号ですが、組み合わせると「2024 を含まない行を全部消す」「縦のデータを横に並べ替える」「数字を3桁ごとにカンマ区切りにする」といったことが一発でできるようになります。

前半(第1〜6章)が検索、後半(第7〜11章)が置換です。正規表現の本領は後半の置換にありますが、そこで使う部品はすべて前半で揃えるので、順番に読んでいただくのがおすすめです。

この記事で分かること

  • | () [] . を使い分けて「候補のどれか」を一度に検索できる
  • {n} {n,m} {n,} ? + * で「何回繰り返すか」を表現できる
  • ^ $ が「文字」ではなく「位置」にマッチすることの意味が分かる
  • \t \r \n \s \d と、それらを使った実務ワザ(縦横変換・表計算ソフトへの貼り付け)が分かる
  • 先読み (?=) (?!) でパスワード検証や「〜を含まない行」の検索ができる
  • 最長一致と最短一致の違いが分かる
  • キャプチャ($1 $2 で日付フォーマット変換・マスキング・CSV の囲み付けができる
  • 数字を3桁ごとにカンマ区切りにする定番の正規表現を、組み立て手順から理解できる

第0章:はじめる前に

正規表現のスイッチを入れる

多くのテキストエディタや IDE の検索・置換ダイアログには「正規表現」のチェックボックスがあります。ここが有効になっていないと、これから紹介する記号はすべてただの文字として扱われます。 逆に言うと、普通の文字列を探したいだけのときはオフにするのが正解です(この使い分けは第4章のエスケープの話でもう一度出てきます)。

この記事の例は、正規表現が使える環境ならおおむねそのまま試せます。手元のエディタでも、コマンドラインの grep -E でも、プログラミング言語の正規表現ライブラリでも構いません。

正規表現には方言がある

最初に断っておきたいのが、正規表現には方言があるということです。ベースになる文法(この記事で扱う . * [] () など)はどこでも共通ですが、細部はエンジンによって差があります。

分かれやすいところ
\d が全角数字にマッチするか する処理系としない処理系がある
\s に全角スペースが含まれるか 含む処理系と含まない処理系がある
置換後の後方参照の書き方 $1 方式と \1 方式がある
$(行末)が改行そのものを含むか 扱いが分かれる

この記事は差が出やすい箇所ではその旨を明示しますが、本番のデータに適用する前に、必ず小さなサンプルで手元の挙動を確かめてください。

バックスラッシュと円マーク

この記事では \t \d のようにバックスラッシュで書きますが、日本語環境のフォントでは \(バックスラッシュ)が ¥(円マーク)として表示されます。見た目が円マークでも、意味はバックスラッシュです。

正規表現の全体像

細かい記号に入る前に、これから学ぶものがどこに位置しているのかを俯瞰しておきます。

正規表現の記号は単体ではあまり役に立ちません。「選択」と「繰り返し」と「位置」を組み合わせて初めて、実務で使える条件になります。この記事も、前半で部品を揃え、後半でそれを組み立てていく流れです。


第1章:選択 ― 「候補のうちのどれか」を一度に探す

この章では、次のような問い合わせ管理台帳を題材にします。チケット番号・担当者・種別・受付日が並んだ、サポート部門でよくあるデータです。

T-1001	渡辺 涼太	返品	2024-03-05
T-1002	高橋 由紀	交換	2024-03-06
T-1003	渡邊 直人	返品	2024-03-07
T-1004	渡部 彩	問合せ	2024-03-08
T-1005	高梨 空	交換	2024-03-09
T-1006	高木 蓮	返品	2024-03-10
T-1007	渡久地 誠	問合せ	2024-03-11

1行目の「渡辺 涼太」と3行目の「渡邊 直人」に注目してください。「辺」と「邊」は別の漢字です。ここが今回の悩みどころになります。

1-1. |(バーティカルバー)― または

「涼太さんの名字、辺だったか邊だったか思い出せない」。こういうとき、渡辺 で検索すると3行目はヒットせず、渡邊 で検索すると1行目がヒットしません。1回ずつ検索して確かめるのは面倒です。

そこで「または」を表す |(バーティカルバー)を使います。

渡辺|渡邊

これで「渡辺 または 渡邊」という意味になり、両方が同時にヒットします。ヒットさえすれば、あとは目視で目的の行を選べばいいわけです。

| は名前以外にも使えます。

返品|交換

これで返品と交換のチケットを同時に抽出できますし、1|2 と書けば「1 または 2」になります。さらにつなげて 1|2|3 と書けば「1 または 2 または 3」です。

正規表現を使わない場合、この「または検索」ができないので 2回・3回と検索を繰り返すことになります。1回で済ませられる、というのが正規表現で作業効率が上がることの本質です。

1-2. ()(丸括弧)― グループ

候補が増えてくると | だけでは冗長になります。

渡辺|渡邊|渡部

3つとも「渡」で始まっているのに、「渡」を3回書いています。今後さらに候補が加わるたびに「渡」を書き足すのも面倒です。そこで共通部分を外に出して、丸括弧でまとめます。

渡(辺|邊|部)

丸括弧はグループを表します。「渡」のあとに「辺・邊・部 のいずれか1文字が続く」という意味になり、先ほどと同じ結果になります。

この書き方にはもう一つ利点があります。曖昧な箇所が丸括弧で囲まれているので、「どこが確定していて、どこが不確かなのか」がひと目で分かります。

1-3. [](角括弧)― 文字クラス

同じことを、もう少し短く書く方法もあります。文字クラスと呼ばれる角括弧です。

渡[辺邊部]

角括弧の中に候補となる文字を並べるだけで、「囲まれた文字のうちのどれか1文字」という意味になります。文字クラスの中では**「または」の | は書きません**(書くと | という文字そのものが候補に加わってしまいます)。

丸括弧と角括弧、どちらが優れているという話ではありません。候補が1文字ずつなら角括弧、候補に2文字以上のものが混ざるなら丸括弧、と考えるのが素直です。

たとえば候補が「渡辺・渡邊・渡久地」の場合を考えてみてください。「久地」は2文字なので、文字クラスでは表現できません。渡[辺邊久地] と書いても「渡のあとに 辺・邊・久・地 のいずれか1文字」という別の意味になってしまいます。

渡(辺|邊|久地)

このように丸括弧でグループにする必要があります。逆に数字の 1・2・3 を探すだけなら、(1|2|3) より [123] のほうが短く、候補が 4・5・6 と増えても [123456] と書き足すだけで済みます。

3つとも「候補のどれか」を表す点は同じで、違うのは書き心地だけです。迷ったら、まず | で素直に書いてみて、長くなってきたら丸括弧か角括弧に整理する、という順番で問題ありません。

1-4. 文字クラスの範囲指定 ― ハイフン

半角数字をすべて探したいとき、[0123456789] と全部書いてもよいのですが、もっと簡単な書き方があります。

[0-9]

文字クラスの中のハイフンは「範囲」を表します。 ハイフンの直前の文字から直後の文字までが対象になるので、[4-6] なら 4・5・6 の3つ、[4-9] なら 4 から 9 までが対象です。

アルファベットも同様です。

書き方 意味
[a-z] 半角英小文字すべて
[A-Z] 半角英大文字すべて
[c-z] c 以降の半角英小文字
[0-9] 全角数字(範囲指定は全角にも使えます)
[0-9a-zA-Z] 半角英数字すべて

先ほどの台帳のチケット番号なら、T-[0-9]{4} のように書けば「T- のあとに半角数字4桁」を狙い撃ちできます。

[0-9a-zA-Z] は実務でよく使う形です。「ログイン ID は半角英数のみ」といったサイトの裏側では、まさにこうした正規表現で入力チェックが行われています。全角の「123」や「XYZ」を弾けるのは、この条件に当てはまらないからです。

ハイフンで範囲が指定できるのは、文字が内部的に文字コードという数値で管理されているからです。a から z までは文字コードが連続しているので、その範囲をまとめて指定できます。深入りすると長くなるので、「裏側に文字コードがいる」とだけ押さえておけば十分です。

1-5. .(ドット)― 何でもいいから1文字

ここまでは「候補が何なのか分かっている」前提でした。では、候補すら分からない場合はどうするか。「担当者の名字は渡で始まるのは確かだけど、渡辺か渡部か渡久地か……」という状況です。

そこで登場するのが .(ドット)です。

渡.

これで「渡」で始まる名字が全員ヒットします。ドットは正規表現において「何でもいいから1文字」を意味します。 渡辺でも渡邊でも渡部でも渡久地でも、渡のあとに1文字あれば当てはまります。

ドットだけで検索すると、全ての文字がヒットします。文字クラスの中にありとあらゆる文字を書き出したもの、と理解しておくと感覚が掴めます。


第2章:繰り返し ― 「何回続くか」を表現する

この章では、次のようなデータを使います。チャットのログを想定した、伸ばし棒(長音記号)の数が1つずつ増えていくデータです。

すごい
すごーい
すごーーい
すごーーーい
すごーーーーい

伸ばし棒が2つのものを探したいとき、素直に すごーーい と打っても検索はできます。しかし 3つ、4つ……と増えるたびに同じ文字を打ち直すのは、正規表現の流儀ではありません。同じ文字が続くときは繰り返しの正規表現を使います。

2-1. {n} ― 指定回数の繰り返し

すごー{2}い

波括弧の中に数字を書くと、直前の文字をその回数だけ繰り返すという意味になります。ここでは波括弧の直前が「ー」なので、「すご」+「ー」2回+「い」、つまり すごーーい だけがヒットします。

{3} なら伸ばし棒3つ、{4} なら4つ。{0} と書くこともでき、その場合は伸ばし棒が0回、つまり すごい がヒットします。

この書き方は、たとえば電話番号の検索に応用できます。03-1234-5678 のような固定電話の文字列を探すなら、こうです。

[0-9]{2}-[0-9]{4}-[0-9]{4}

「半角数字2回 → ハイフン → 半角数字4回 → ハイフン → 半角数字4回」という意味になります。

2-2. {n,m} ― 範囲回数の繰り返し

伸ばし棒が2〜4個のものをまとめて探したいときは、波括弧の中にカンマ区切りで下限と上限を書きます。

すごー{2,4}い

これで「直前の文字を 2回以上4回以下繰り返す」という意味になり、すごーーい から すごーーーーい までが同時にヒットします。{0,100} と書けば 0回以上100回以下です。

第1章の台帳にも応用できます。渡. では「渡久地」さんは3文字目までしか一致しません(部分一致でヒットはしますが、名字全体を掴めません)。かといって 渡.. では渡久地さんしかヒットしません。

渡.{1,2}

こう書けば、「渡のあとに、何でもいい1文字を1回以上2回以下繰り返す」となり、渡辺・渡邊・渡部・渡久地が同時にヒットします。

2-3. {n,} ― 上限なしの繰り返し

「2回以上なのは確かだが、上限が何回か分からない」ことは実務でよくあります。ログの区切り線が何十文字続いているか、いちいち数えていられません。そういうときは上限を省略します。

すごー{2,}い

カンマの右側を省略すると「直前の文字を2回以上繰り返す」という意味になります。上限は無制限です。

名字の例では、渡.{1,2} だと4文字以上の名字を取りこぼします。

渡.{1,}

これなら「渡のあとに何でもいい1文字が1回以上続く」、つまり渡で始まる名字すべてという意味になります。

2-4. ? + * ― よく使う回数の省略記法

{n,m} はどんな回数でも表現できますが、頻出する3パターンには専用の記号が用意されています。

記号 意味 波括弧で書くと
? 0回以上1回以下(あってもなくてもよい) {0,1}
+ 1回以上 {1,}
* 0回以上 {0,}

* は「あってもなくていい、いくつでも」という最も緩い指定です。後の章で出てくる .*(何でもいいから0文字以上)は、この組み合わせで、実務で最も見かける表現になります。

2-5. () でまとめて繰り返す

ここまでの繰り返しは、すべて直前の1文字が対象でした。では「パチ」のような塊を繰り返したいときはどうするか。次のデータで考えます。

ゴール。
ゴールパチ。
ゴールパチパチ。
ゴールパチパチパチ。

第1章で出てきた丸括弧が、ここでも効きます。

ゴール(パチ){2}。

丸括弧で囲むと、その塊があたかも1文字であるかのように繰り返しの対象になります。 これで「パチ」を2回繰り返して「。」で終わる行、つまり3行目がヒットします。

同じ要領で、(パチ){1,3} なら1回以上3回以下、(パチ){2,} なら2回以上、(パチ)? なら0回か1回、(パチ)+ なら1回以上、(パチ)* なら0回以上です。

これを電話番号の例に戻して使うと、冗長さを減らせます。先ほどの条件をよく見ると、-[0-9]{4} の部分がまったく同じ形で2回登場していました。

[0-9]{2}(-[0-9]{4}){2}

同じ意味のまま、繰り返し部分を1回の記述にまとめられました。同じことを何度も書く状態を冗長と呼び、正規表現でもプログラミングでも基本的には避けられるものです。


第3章:位置 ― 「行のどこにあるか」を指定する

この章から「位置(アンカー)」の話に入ります。ここは正規表現の中でも最初はとっつきにくい部分ですが、文字ではなく位置にマッチするという一点さえ掴めば大丈夫です。

題材は経費精算のデータです。「2024-01-15 に消耗品を 3200 円で購入した」という意味の行が並んでいます。

2024-01-15	消耗品	3200
2024-02-03	書籍	1800
2025-06-20	備品	2024
2026-03-11	雑費	560

3-1. ^(キャレット)― 前方一致

ここから「2024 年のデータ」を抜き出したいとします。単純に 2024 で検索すると、1〜2行目の日付だけでなく、3行目の金額 2024 円もヒットしてしまいます。日付の 2024 なのか、金額の 2024 なのかを区別できません。

ここで規則性に注目します。日付の 2024 は必ず行の先頭にあり、金額の 2024 が行頭に来ることはありません。この規則性を使います。

^2024

^(キャレット)は行頭を表します。これで「2024 で始まる行」という意味になり、日付が 2024 年の行だけが取り出せます。

ここで大事なのが、キャレットは文字ではなく「位置」にマッチするという点です。^ だけで検索すると各行の先頭がハイライトされたように見えますが、カーソルは移動しても文字は一切選択されません。行の先頭という位置に当たっているだけです。

行の先頭の「文字」がほしいなら、位置と文字を組み合わせます。

^.

「行頭の直後に、何でもいい1文字」なので、各行の1文字目が取れます。

3-2. $(ドル)― 後方一致

では逆に、金額が 2024 円の行(3行目)だけを探すにはどうするか。行末を表す $(ドル)を使います。

2024$

これで「2024 で終わる」という意味になり、3行目の金額部分だけがヒットします。$ もキャレットと同じく位置にマッチする正規表現で、文字にはマッチしません。行末の1文字がほしければ .$ と書きます。

3-3. ^$ で完全一致 ― 行全体を掴む

行頭と行末を組み合わせると、行全体を検索できます。

^.*2024$

先頭から読むと、「行頭 → 何でもいい1文字を0回以上繰り返す → 2024 という文字 → 行末」。つまり 2024 で終わる行の全体という意味です。

2024$ だけだと、その 4文字しか選択されません。目的が「2024 円の経費を見たい」であれば、行全体がハイライトされたほうが目的に合っています。

このように ^ で始まり $ で終わる正規表現が「完全一致」 の形です。同じ理屈で、2024 で始まる行の全体はこう書けます。

^2024.*$

3-4. ^.*〜.*$ ― 「〜を含む行」の検索

行頭でも行末でもなく、行のどこかに含まれていればよい、という条件が実務では一番多いはずです。検索したい文字を .* で挟みます。

^.*2024.*$

「行頭 → 何でもいい0文字以上 → 2024 → 何でもいい0文字以上 → 行末」。.* は 0文字でも成立するので、次のどのパターンもヒットします。

2024-01-15	消耗品	3200    ← 行頭にある
2025-06-20	備品	2024     ← 行末にある
MEMO2024BACKUP                   ← 行の途中にある
2024                             ← それしかない

この形は使い回しが効きます。画面からはみ出すような長い行でも、「この行のどこかに 2024 が入っている」ことが分かります。後半の「〜を含まない行」も、この形を土台にして作っていきます。


第4章:目に見えない文字と、記号そのものの検索

ここからは応用編です。単体の記号ではなく、これまでの知識を組み合わせて実務で使っていきます。

4-1. 改行 ― \r\n

画面では改行は「折り返し」に見えますが、改行も文字の一種です。ファイルの中には改行文字というデータが埋め込まれていて、それを検知した箇所で表示が折り返されています。

ただし改行文字はコピーして検索欄に貼り付けても検索できません。改行を検索するには正規表現を使う以外に方法がありません。

さらにややこしいことに、改行文字は1種類ではありません。

改行コード 正規表現 主な環境
CR \r 古い Mac など
LF \n Linux / macOS
CR+LF \r\n Windows

Windows 環境では CR+LF が主に使われます。改行コードが分からないファイルで、すべての改行をまとめて検索したい場合はこう書きます。

[\r\n]+

改行まわりの挙動は方言が出やすい部分です。$(行末)が改行そのものを含むのか、位置だけを指すのかはツールによって異なりますし、複数行にまたがるパターンをそもそも扱えない検索機能もあります。置換で使う前に、必ず小さなサンプルで動作を確かめてください。

実践:データの縦横変換

改行の検索ができると、置換と組み合わせて強力なことができます。

  • 置換前:[\r\n]+
  • 置換後:,

これを一括置換すると、縦に並んでいたデータがカンマ区切りで横一列になります。いわゆる縦横変換です。この状態で保存すれば、そのまま CSV として使えます。

4-2. タブ ― \t

タブ文字は、一定間隔(多くの環境では4文字おき)に引かれた縦線の、次の位置まで空白を挿入する文字です。次のように打つと、タブが「何文字分」になるかが変わることが分かります。

1	赤
22	青
333	緑
4444	白

1文字目のあとのタブは3文字分、2文字までのあとは2文字分、4文字までのあとは4文字分の空白になります。プログラミングではインデント(字下げ)に使われ、日常業務では 表計算ソフトからコピーしたデータのセル区切りとして現れます。

正規表現では \t と書きます。

\t

実践:表計算ソフトに直接貼り付けられる形にする

先ほどの縦横変換で、置換後をカンマではなく \t にしてみます。

  • 置換前:[\r\n]+
  • 置換後:\t

こうすると、縦に並んでいたデータがタブ区切りで横に並びます。表計算ソフトからコピーしたデータがタブ区切りだったことを思い出してください。この原理を逆に使えば、タブ区切りの文字列を表計算ソフトに貼り付けると、それぞれ別のセルに入ります。

4-3. 空白 ― \s

タブ、CR、LF に加えて、半角スペースと全角スペースもまとめて探したいことがあります。文字クラスで並べればこう書けます。

[\t\r\n  ]

(角括弧の中に半角スペースと全角スペースを入れています)

同じことが、たった2文字で書けます。

\s

\s空白文字を表します。大文字にした \S は逆に「空白文字以外」という意味になり、先ほどヒットしなかった箇所がすべてヒットします。

\s に全角スペースが含まれるかは処理系によって分かれます。半角スペースとタブだけを対象にしたいなら、[ \t] のように明示的に書くほうが安全です。

4-4. 数字 ― \d

半角数字は [0-9] で表せましたが、これも短縮形があります。

\d

ここも方言の出るところで、\d が全角数字にマッチするかどうかは処理系によって異なります

処理系 \d が全角数字にマッチするか
Python(re する
JavaScript しない
Java(既定) しない

短く書けるので、先ほどの電話番号の条件もすっきりします。

\d{2}(-\d{4}){2}

Web の入力チェックなど、半角と全角を厳密に区別しなければならない場面で \d に頼るのは避けてください。 処理系によっては全角数字が検証をすり抜け、そのままデータベースに登録されてしまいます。この用途では [0-9] と明示するのが確実です。一方、CSV やログを手早く眺めたいだけなら、半角全角の区別に意味はないので \d が便利です。使い分けが大切です。

なお \D と大文字にすると「数字以外」という意味になります。\s / \S\d / \D のように、大文字にすると否定になるのは共通のルールです。

4-5. エスケープ ― 記号そのものを検索する

ここまでで . | ( ) [ ] { } ^ $ \ といった記号に特別な意味を持たせてきました。では、これらの記号そのものを検索したいときはどうするか。直前にバックスラッシュを付けます。

検索したい文字 書き方
\ \\
. \.
( ) \( \)
$ \$
\t という文字列そのもの \\t

このように、正規表現として意味のある文字の直前に置いて、特殊な意味を打ち消す文字をエスケープ文字といいます。

たとえばソースコードから getTime()() という文字列を探したいとき、() と書くと丸括弧がグループ化として解釈され、意図しない箇所までヒットします。\(\) と書けば、丸括弧を文字として検索できます。

とはいえ、() を検索するだけなら正規表現のスイッチをオフにするほうが速いです。作業効率を上げたくて正規表現を学んでいるなら、チェックを外すだけで済む場面でエスケープを書くのは本末転倒です。

エスケープが本当に必要なのは、エスケープ以外の理由でも正規表現を使う場面です。たとえば「丸括弧で囲まれた数字」を探したいとき。

\(\d+\)

\d+(1文字以上の数字)を使う以上、正規表現は必須です。だから丸括弧のほうもエスケープする、という順番になります。いつでも正規表現が優れているわけではない、という点は覚えておいてください。


第5章:先読み ― 「〜を含む/含まない」を条件にする

ここからは一段レベルが上がります。「含む」「含まない」を条件として重ねる書き方です。

5-1. 否定の文字クラス ― [^...]

まずは簡単なほうから。次のような単語のリストで考えます。

りんご
みかん
ぶどう
すいか

第3章で「〜を含む行」は ^.*ん.*$ と書けました。その逆、「ん を含まない行」はこう書きます。

^[^ん]*$

文字クラスの角括弧の中で、先頭にキャレットを書くと「否定」の意味になります。 [^ん] は「ん 以外の1文字」です。

分解して読むと、「行頭 → ん 以外の文字を0回以上繰り返す → 行末」。行の最初から最後まで「ん 以外」で埋め尽くされている、つまり ん が1文字も含まれない行という意味になり、ぶどうすいか が残ります。

否定の文字クラスには複数の文字を書けます。

^[^んう]*$

これで「ん でもなく う でもない」、つまりん または う を含まない行となり、残るのは すいか だけです。

ただし、この方法には限界があります。単独の文字に対してしか使えません。 [^2024] と書くと「2・0・4 のいずれでもない文字」という意味になってしまい、「2024 という連続した文字列を含まない」にはなりません。そこで次の否定先読みが必要になります。

キャレットは「行頭」と「文字クラスの否定」で意味が変わります。角括弧の中の先頭にあるときだけ否定、それ以外は行頭、と覚えておくと混乱しません。

5-2. 肯定先読み (?=...) ― パスワードの正規表現

先読みの実用例として、パスワードの検証を作ってみます。Web サービスでは、ユーザーが入力したパスワードがふさわしいかどうかを検証し、条件を満たさなければエラーとして弾いています。この検証ルールをパスワードポリシーと呼び、多くの場合その検証には正規表現が使われています。

ここでは次のポリシーを実装してみます。

  • 使用できる文字は半角英数と一部の記号のみ
  • 8文字以上16文字以下
  • 半角英小文字・英大文字・数字・記号をそれぞれ1回以上使う
  • さらに記号は2回以上使う

ステップ1:使える文字種類を決める

文字クラスで「使ってよい文字」を列挙し、完全一致(^$)で挟みます。

^[a-zA-Z0-9!?#%@_-]+$

これで、ここに挙げた文字だけで構成されている行がヒットします。ポリシー外の文字が1つでも混ざればマッチしなくなり、それがそのまま入力エラーになります。

文字クラスの中に -(ハイフン)を入れたいときは、末尾に置くのが定石です。途中に書くと「範囲指定」として解釈されてしまいます。

ステップ2:文字数を決める

いまの条件は +(1文字以上)なので、1文字でも無限に長くても通ってしまいます。範囲回数の繰り返しに置き換えます。

^[a-zA-Z0-9!?#%@_-]{8,16}$

これで 8文字未満・17文字以上は弾かれます。

ステップ3:各文字種を最低1回使わせる(肯定先読み)

まだ「英小文字だけ」「数字だけ」のパスワードが通ってしまいます。そこで肯定先読み (?=...) を使います。

^(?=.*[a-z])[a-zA-Z0-9!?#%@_-]{8,16}$

(?=) で囲まれた部分が肯定先読みで、中の .*[a-z] は「どこかに半角英小文字がある」という、これまで学んできた普通の正規表現です。

肯定先読みを含む正規表現は、2段構えの条件になります。「先読みの中の条件」と「先読みを除いた残りの条件」の両方を満たすものだけがヒットします。同じ要領で条件を重ねていきます。

^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=(.*[!?#%@_-]){2})[a-zA-Z0-9!?#%@_-]{8,16}$

これで「英小文字を1回以上」「英大文字を1回以上」「数字を1回以上」「記号を2回以上」「使える文字だけで8〜16文字」という5段構えになりました。

図のとおり、先読みは「関門」を1つずつ増やしていくイメージです。関門をすべて通過し、最後に本体の条件も満たしたものだけが合格になります。

なお、肯定先読みは ^$ と同じく「位置」を表す正規表現です。(?=.*[a-z]) だけを単独で検索すると、「その後ろに半角英小文字が登場する位置」がヒットします。後ろに英小文字が1つもない位置ではヒットしません。文字を消費せずに条件だけを確かめる、というのが先読みの正体です。

5-3. 否定先読み (?!...) ― 「〜を含まない行」

肯定先読みの逆が否定先読み (?!...) です。丸括弧の中の条件を満たさない位置にマッチします。

第3章の経費データに戻って、「2024 という連続した文字列を含まない行」はこう書きます。

^(?!.*2024).*$

これも2段構えです。

  • 先読みの条件:.*2024(どこかに 2024 がある)→ これを満たさないこと
  • 残りの条件:^.*$(どんな行でも当てはまる)→ これを満たすこと

2024 を含む行は必ず .*2024 にマッチしてしまうので、否定先読みで弾かれます。結果として「2024 を含まない行」だけが残ります。

同じ形で、否定の文字クラスの例も書き換えられます。

^(?!.*ん).*$

否定先読みは否定の文字クラスの上位互換です。ただし文字数を比べると、単独の1文字を対象にするなら ^[^ん]*$ のほうが短く済みます。連続した文字列が相手なら否定先読み、1文字だけなら否定の文字クラス、という使い分けになります。

実践:いらない行を一括削除する

否定先読みに改行の正規表現を足すと、置換で行ごと消せます。

  • 置換前:^(?!.*2024).*$\r?\n?
  • 置換後:(空欄)

これを一括置換すると、2024 を含まない行がすべて削除され、2024 を含む行だけが残ります。CSV やログを見やすくするために、一時的にいらない行を消す。地味ですが、日常的に効く技です。

この置換は元のファイルを大きく書き換えます。ログや納品物に対して実行する前に、必ずコピーを取ってから試してください。


第6章:最短一致 ― 欲張らせない

次のような HTML から、タグの部分だけを抜き出したいとします。

<p class="lead">本日は<b>満席</b>のため<b>予約</b>を締め切りました</p>

これまでの知識で書くと、こうなりそうです。

<.*>

ところが実際に検索すると、行の最初の < から最後の > まで丸ごと、つまり1行すべてがヒットしてしまいます。タグではない日本語の本文まで巻き込まれています。

原因は . にあります。ドットは「何でもいいから1文字」なので、>< 自体もドットに当てはまります。その結果、.* がどこまでも伸びていき、途中の > を飲み込んでしまうわけです。

この挙動を最長一致といいます。1つ目の > を見つけても検索を終わらせず、さらに先を探し、もう見つからないところまで進んでから最後の候補に戻ってマッチします。

今回やりたいのは逆で、最初の > が見つかった時点で打ち切ってほしい。そのためには、繰り返しの記号の後ろに ? を付けます。

<.*?>

これで最短一致になり、<p class="lead"> <b> </b> … がそれぞれ別々にヒットします。

? を付けて最短一致にできるのは * だけではありません。+?{1,5}? のように、繰り返しの正規表現全般に使えます。

書き方 挙動
<.*> 最長一致。行内の最初の < から最後の > まで
<.*?> 最短一致。中身が空の <> にもマッチ
<.+?> 最短一致だが1文字以上。<> にはマッチしない
.{1,5}? 5文字ではなく1文字でマッチしようとする

解説書によっては、最長一致を欲張り(greedy)、最短一致を**控えめ(lazy)**と呼んでいるものもあります。何も指定しなければ最長一致になる、という既定の挙動だけは押さえておいてください。


第7章:正規表現置換 ― 引っかかった箇所を書き換える

ここからが後半戦です。正規表現置換は、正規表現で引っかかった箇所を別の文字に置き換える機能です。

簡単な例から見ていきます。次のようなデータがあるとします。

アイウエオ
カキクケコ
サシスセソ
  • 置換前:[アイウ]
  • 置換後:

これを一括置換すると、 がそれぞれ に置き換わります。正規表現に引っかかった箇所が、置換後の文字列に置き換わる。それだけの話です。

位置を置換するとどうなるか

面白いのはここからです。第3章で学んだ「位置」を表す正規表現でも置換できます。CSS のプロパティ一覧のような、行末にセミコロンを付けたいデータで試してみます。

color: red
margin: 0
padding: 8px
  • 置換前:$
  • 置換後:;

こうすると、各行の行末にセミコロンが挿入されます$ は文字ではなく位置なので、置き換えるものがなく、結果として「挿入」になるわけです。

そして、この置換は何度でも実行できます。行末に文字を挿入しても、その後ろがまた新しい行末になるからです。位置にマッチする正規表現の性質が、置換を通してはっきり見えてきます。

ただし、ここまでの理解で止まってしまうと、正規表現置換について語ることはほとんどなくなってしまいます。次の章のキャプチャを使いこなせて、はじめて正規表現置換が分かったと言えます。


第8章:キャプチャ ― マッチした箇所を置換後に呼び出す

キャプチャとは、置換の際にマッチした箇所を、置換後の文字列で再利用できる機能です。この章はこの記事全体の中でも重要な位置にあたるので、腑に落ちるまで読み返してみてください。

題材として、4文字の言葉を逆順に並べ替えることを考えます。

きたかぜ
はるいろ
ふゆぞら

これを ぜかたき のようにしたい、という話です。

まずは検索条件から

4文字の言葉を表す正規表現は、第2章と第3章の知識でこう書けます。

^.{4}$

ところが逆順にしたい場合は、同じ意味でも次のように書き直します。

^(.)(.)(.)(.)$

検索としては、この2つはまったく同じ意味です。丸括弧の中がドット1文字だけなので、グループ化の意味がないようにも見えます。実際、単に検索するだけなら丸括弧を書く意味はありません。

意味が生まれるのは、置換に移ったときです。

$1 $2 で呼び出す

キャプチャというのは、この丸括弧のことです。丸括弧にマッチした文字は、置換後の文字列で $1 $2 … と書くことで呼び出せます。番号は、正規表現を左から読んでいって丸括弧が登場した順番です。

きたかぜ に対して先ほどの正規表現がどうマッチするかを整理すると、こうなります。

丸括弧 マッチする文字 置換後の呼び出し方
1つ目 $1
2つ目 $2
3つ目 $3
4つ目 $4

試しに置換後を $1 とだけ書いて一括置換すれば、各行は先頭の1文字だけになります。$1$2 なら きた$2$1 なら たき です。

ということは、答えはこうです。

  • 置換前:^(.)(.)(.)(.)$
  • 置換後:$4$3$2$1

これで4文字の言葉が逆順に並べ替えられます。

置換後で後方参照をどう書くかは方言が出ます。$1 方式が主流ですが、コマンドラインの sed などは \1 方式です。手元のツールのドキュメントで確認してください。

「キャプチャですか、だから何?」と思われたかもしれません。ここから先の章は、そのキャプチャが実務でどう効くのかの具体例です。


第9章:キャプチャの実践 ― 4つの定番パターン

9-1. 日付フォーマットの変換

海外製システムから出力された、米国式書式(月/日/年)の日付が並んでいるとします。日本式(年/月/日)に慣れた目には読み違えやすい形式です。

03/15/2026
11/04/2026
07/22/2026

数字の各部位を丸括弧で囲んでキャプチャします。

  • 置換前:^([0-9]{2})/([0-9]{2})/([0-9]{4})$
  • 置換後:$3/$1/$2

これで 2026/03/15 の並びになります。置換後を次のようにすれば、さらに読みやすくなります。

  • 置換後:$3年$1月$2日

同じ考え方で、システム開発でよく見る YYYYMMDDhhmmss 形式も扱えます。20260315094500 のように数字が14個並んでいるだけの文字列ですが、先頭から4桁・2桁・2桁・2桁・2桁・2桁が年月日時分秒だと分かっていれば、部位ごとにキャプチャできます。

  • 置換前:^([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})$
  • 置換後:$1-$2-$3 $4:$5:$6

結果は 2026-03-15 09:45:00 です。置換前で丸括弧に囲んでしまえば、置換後では自由に並べ替えられる、というのがキャプチャの威力です。

9-2. 大文字・小文字の変換

キャプチャした文字は、そのまま使うだけでなく変換して使えることがあります。Perl 系の記法に対応した処理系であれば、次のように書けます。

大文字を小文字にするなら、こうです。

  • 置換前:([A-Z])
  • 置換後:\l$1

逆に小文字を大文字にするなら、こうです。

  • 置換前:([a-z])
  • 置換後:\u$1

覚え方は英単語です。小文字にする \l は lower の l、大文字にする \u は upper の u

書き方 効果
\l / \u 直後の1文字だけを小文字 / 大文字にする
\L...\E / \U...\E \E が現れるまでを小文字 / 大文字にする

今回はキャプチャが1文字なので \l$1 でも \L$1 でも同じ結果になりますが、複数文字をまとめて変換したいときは \U$1\E のように書きます。

\l \u \L \U対応していない処理系もあります(JavaScript の正規表現には置換時の大文字小文字変換がありません)。また、エディタによっては「選択範囲を大文字/小文字に変換」というメニュー機能が用意されており、単に変換したいだけならそちらのほうが早いです。

9-3. 名前のマスキング

個人情報を扱うとき、生のデータをそのまま誰にでも見せるのは情報漏えいのリスクになります。そこで、ぼかした形で表示するマスキングという処理がよく使われます。

第1章の台帳から担当者名だけを抜き出したデータで考えます(名字と名前は全角スペース区切りです)。

渡辺 涼太
高橋 由紀
渡邊 直人

名字の先頭と名前の先頭を に置き換えて、*辺 *太 のようにします。置換したいのは「行頭の次の文字」と「全角スペースの次の文字」なので、こう書きます。

  • 置換前:(^| ).(丸括弧の中は、キャレット・バーティカルバー・全角スペース)
  • 置換後:$1*

マッチのしかたには2パターンあります。

パターン $1 の中身 置き換わる文字
行頭にマッチ 空(位置なので文字がない) 名字の1文字目
全角スペースにマッチ 全角スペースそのもの 名前の1文字目

位置を表す ^ をキャプチャすると、$1 は空文字になります。 一方、全角スペースは文字なのでそのまま $1 に入ります。置換後で $1 を書き戻しているおかげで、区切りの全角スペースが消えずに残る、というわけです。

「これだと一部が見えてしまう」という場合は、キャプチャとは関係ありませんが次の方法で全部潰せます。

  • 置換前:\S
  • 置換後:

空白以外の文字がすべて になります。

マスキングは元データを書き換える不可逆な処理です。必ず作業用のコピーに対して実行し、個人情報の取り扱いは所属先の規程に従ってください。

9-4. 囲みなし CSV を囲みありに変換する

第3章で使った経費精算のデータを、カンマ区切りにしたものを考えます。CSV はカンマで区切られたデータで、1行が1件を表し、区切られた一つ一つをフィールドと呼びます。

2024-01-15,消耗品,3200
2024-02-03,書籍,1800

各フィールドをダブルクォートで囲むかどうかは、参画するシステムや案件ごとに取り決められます。

囲みあり 囲みなし
メリット データの中にカンマや改行を含められる データ量が少なく、打つ手間もない
デメリット ダブルクォートの分だけ増える データ内でカンマを使うと区切りと区別できない

たとえば金額を 3,200 と3桁区切りで持ちたい場合、囲みなしではそのカンマが区切りなのか桁区切りなのか判別できません。囲みありなら「ダブルクォートの外のカンマが区切り」と決められるので区別がつきます。

この変換をキャプチャで行います。まず「カンマとカンマの間のデータ」を表す正規表現を考えます。第6章の最短一致の出番です。

.*?,

これで「カンマが見つかるまで、何でもいいから0文字以上」となり、各フィールドにマッチします。ただし最後のフィールド(金額)は後ろにカンマが続かないのでヒットしません。そこで、区切りの条件を「カンマまたは行末」に広げ、囲みたい箇所を丸括弧でキャプチャします。

  • 置換前:(.*?)(,|$)
  • 置換後:"$1"$2

$1 がフィールドの中身、$2 が区切り文字(カンマまたは行末)です。$2 はそのまま書き戻すので、区切りの構造は保たれたまま、中身だけがダブルクォートで囲まれます。

この置換は行末でも成立するため、環境によっては行の末尾に空の "" が余分に付くことがあります。実行後は結果を確認し、余計な箇所が出たら追加で除去してください。空のフィールドを気にしなくてよいデータなら、置換前 ([^,]+) / 置換後 "$1" というもっと単純な書き方でも同じ結果になります。


第10章:総合演習 ― 数字を3桁ごとにカンマ区切りにする

正規表現の文法として新しく学ぶことは、この章が最後です。ここまでの内容をフルに使うので結論はやや複雑ですが、実用性は抜群なので、結論だけでも持ち帰ってください。

画面に金額や距離を表示するとき、数値は3桁おきにカンマが入っているのが普通です。カンマの入っていない数値に対して、正規表現置換で一括挿入します。

850
9800
125000
7250000
980000000
-4500
3200円

目指す完成形はこうです。

850
9,800
125,000
7,250,000
980,000,000
-4,500
3,200円

5つのステップで組み立てていきます。

ステップ1:3桁の数字を表す

[0-9]{3}

これは左から数えて3桁にマッチします。9800 なら 980 の部分に当たってしまい、800 にはマッチしません。正規表現では、一度マッチした箇所を再利用して別のマッチを作ることはないからです。カンマは右から数えて入れるものなので、これでは困ります。

ステップ2:右から数えて3桁にする

[0-9]{3}(?![0-9])

第5章の否定先読みを使いました。意味は「3文字の数字が続いた後に、数字以外が続く箇所」です。

9800 で考えてみます。980 は前半部分にマッチしますが、その後に 0 という数字が続くので否定先読みの条件を満たしてしまい、全体としてはマッチしません。一方 800 は、その後に行末が来て数字が続かないので、否定先読みの条件を満たさず、全体にマッチします。これで右から数えて3桁を掴めました。

ステップ3:「桁」ではなく「位置」にする

いま掴んでいるのは数字そのものですが、やりたいのは数字と数字の間にカンマを挿入することです。そこで全体を肯定先読みで囲み、「その開始位置」に変換します。

(?=[0-9]{3}(?![0-9]))

意味は「3文字の数字が続いた後に数字以外が続く、その位置」です。この位置をカンマに置換すると、7250000980000000 はうまく処理できますが、2つ問題が残ります。

  1. 850 のような3桁ちょうどの数字の先頭にもカンマが入ってしまう
  2. 右から6桁目・9桁目の位置にはカンマが入らない

ステップ4:\B で先頭のカンマを防ぐ

問題1を解決するために、新しい正規表現を導入します。

\B(?=[0-9]{3}(?![0-9]))

\b単語の境界の位置を、\B単語の境界以外の位置を表します。たとえば We are not alone という文に対して \b で検索すると、各単語の先頭と末尾の位置にマッチします。\B はその逆で、単語の途中の位置にマッチします。

数値データの先頭は単語の境界にあたるので、\B を付けることで「数字の先頭にはカンマを入れない」が実現できます。-45003200円 のように符号や単位が付いていても、数字の始まりは境界なので同じく守られます。

記号 意味
\b 単語の境界の位置
\B 単語の境界以外の位置

\s / \S\d / \D と同じく、大文字にすると否定になるルールがここでも効いています。

ステップ5:+ で3の倍数の桁に広げる

残る問題2です。ステップ4までは「3桁」限定でした。6桁なら [0-9]{6}、9桁なら [0-9]{9} と書けばそれぞれの位置にカンマを入れられますが、桁が増えるたびに条件を書き足すのは冗長です。

第2章の「まとめて繰り返す」で解決します。

\B(?=([0-9]{3})+(?![0-9]))

([0-9]{3})+ は「3桁の数字をひとまとまりとして1回以上繰り返す」、つまり3の倍数の桁数の数字という意味です。全体では「単語の境界以外の位置であって、そこから先が3の倍数の桁の数字で終わっている位置」となり、右から3桁おきの位置がすべて拾えます。

最終形はこうです。

  • 置換前:\B(?=([0-9]{3})+(?![0-9]))
  • 置換後:,

\d が使える環境なら \B(?=(\d{3})+(?!\d)) と短く書けます(全角数字の扱いには注意してください)。

まとめて繰り返し・否定先読み・肯定先読み・\B と、この記事で扱ったものが総動員されています。一度で理解しきれなくても問題ありません。「正規表現置換を使えば数値を3桁カンマ区切りにできる」と、この1行を覚えておくだけでも十分に元が取れます。


第11章:複数ファイルをまたいで検索・置換する

ここまでは1つのファイルの中の話でした。しかし実務では、設定やソースが複数のファイルに分かれているのが普通です。「この設定値を使っている箇所を全部知りたい」といった影響範囲の調査では、ファイルを横断した検索が必要になります。

この用途の代表格が grep です。名前は「global regular expression print(全体から、正規表現に一致する行を、表示する)」に由来すると言われています。もとは Unix 系のコマンドですが、多くのエディタや IDE にも「フォルダ内検索」「Grep」といった名前で同等の機能が用意されています。

簡単な例で試してみます。あるフォルダに2つの設定ファイルを置きます。

api.conf

host=api.example.local
timeout=30
retry=3

cache.conf

host=cache.example.local
timeout=60
maxsize=512

ここから「タイムアウトの設定行」を探します。条件は ^timeout=[0-9]+$、つまり「行頭 → timeout= → 半角数字1文字以上 → 行末」です。

grep -E '^timeout=[0-9]+$' *.conf

どのファイルの何行目でヒットしたかが分かるのがポイントで、エディタの機能を使う場合は結果からその行へジャンプできるものがほとんどです。

エディタの検索ダイアログでも、指定する項目はだいたい共通です。

項目 指定内容
検索条件 ^timeout=[0-9]+$
フォルダ 対象ファイルを置いたフォルダのパス
ファイル *.conf
除外ファイル / 除外フォルダ バイナリや .git などを既定で除外

「ファイル」欄の *.conf は正規表現ではなくワイルドカードです。ここでのアスタリスクは「何でもいいから0文字以上」を意味するので、*.conf は「拡張子が conf のもの」、*.* は「ファイル名にドットを含むもの」になります。正規表現とは別物なので混同しないよう注意してください。

一括置換に進む

検索でマッチした箇所に対して、そのまま置換までかけられます。第8章のキャプチャがそのまま使えるので、たとえば「タイムアウト値だけを 90 に統一し、キー名はそのまま残す」ならこうです。

  • 置換前:^(timeout)=[0-9]+$
  • 置換後:$1=90

コマンドラインなら次のように書けます(sed は後方参照が \1 方式で、-i.bak を付けると元のファイルが .bak として残ります)。

sed -i.bak -E 's/^(timeout)=[0-9]+$/\1=90/' *.conf

第9章の「囲みなし CSV を囲みありに変換する」置換をこの形で流せば、フォルダ内の CSV をまとめて囲みありに変換することもできます。

一括置換は、一度の操作で多数のファイルを書き換えます。実行前に 対象フォルダ・ファイル指定・ヒット件数 を必ず確認してください。先に検索だけを実行して結果を目で確かめ、それから置換に進むのが安全な順番です。バックアップを作るオプションがあるなら、特に理由がないかぎり有効にしておくことをおすすめします。

最後に余談をひとつ。開発現場ではセキュリティ上の理由で、メンバーが自由にツールをインストールできないことがあります。そうなると目視でファイルを追うしかなく、これを「目で grep する」ということで目 grep と呼んだりします。作業効率という観点では、できれば避けたい状況です。


まとめ:正規表現 早見表 & チェックリスト

早見表

選択(どれか)

記号 意味
| または(バーティカルバー)
(...) グループ。まとめて扱う
[...] 文字クラス。囲まれた文字のいずれか1文字
[^...] 否定の文字クラス。囲まれた文字以外の1文字
[a-z] 範囲指定(ハイフン)
. 何でもいいから1文字

繰り返し(何回)

記号 意味
{n} ちょうど n 回
{n,m} n 回以上 m 回以下
{n,} n 回以上(上限なし)
? 0回以上1回以下
+ 1回以上
* 0回以上
*? +? 最短一致

位置(どこ)

記号 意味
^ 行頭
$ 行末
(?=...) 肯定先読み
(?!...) 否定先読み
\b / \B 単語の境界 / 単語の境界以外

特殊文字

記号 意味
\t タブ
\r / \n CR / LF
\s / \S 空白文字 / 空白文字以外
\d / \D 数字 / 数字以外
\ エスケープ(次の記号の特殊な意味を打ち消す)

置換後で使うもの

記号 意味
$1 $2 キャプチャした箇所を左から順に呼び出す(\1 方式の処理系もあり)
\l / \u 直後の1文字を小文字 / 大文字にする
\L...\E / \U...\E 範囲をまとめて小文字 / 大文字にする

よく使う組み合わせ

目的 書き方
〜で始まる行 ^〜.*$
〜で終わる行 ^.*〜$
〜を含む行 ^.*〜.*$
〜を含まない行 ^(?!.*〜).*$
タグや引用符の中身だけ取る <.*?>
固定電話番号 [0-9]{2}(-[0-9]{4}){2}
日付を年月日の並びに 置換前 ^(\d{2})/(\d{2})/(\d{4})$ / 置換後 $3/$1/$2
CSV を囲みありに 置換前 ([^,]+) / 置換後 "$1"
数字を3桁カンマ区切りに 置換前 \B(?=(\d{3})+(?!\d)) / 置換後 ,

チェックリスト

  • 検索欄の「正規表現」を有効にした(逆に、記号を文字として探すときはオフにする)
  • 候補が1文字なら []、2文字以上が混ざるなら () を選べている
  • 繰り返しは同じ文字を並べず {n} {n,m} {n,} ? + * で書けている
  • ^$ が「文字」ではなく「位置」であることを理解している
  • 半角と全角を区別すべき場面で \d \s に頼っていない([0-9] などに置き換えた)
  • 囲まれた中身を取るときは .*? で最短一致にしている
  • 置換後で再利用したい部分を、置換前で丸括弧に囲んである
  • 置換を実行する前に、対象ファイルのコピーを取った
  • 方言が出やすい箇所(\d の範囲、後方参照の記法)を手元の環境で確かめた

正規表現は、覚えた記号の数より「同じ作業を2回やりそうになったら1回にまとめられないか考える」という発想のほうが大事です。渡辺さんと渡邊さんを別々に探していたのが1回で済む。日付の並べ替えを1行ずつ手で直していたのが1回の置換で終わる。それだけのことですが、日々の作業の中で積み重なると効いてきます。まずは手元のログや CSV のコピーを開いて、^.*$ を組み合わせるところから遊んでみてください。慣れてきたら、丸括弧で囲んで $1 を呼び出す。そこまで来れば、正規表現は道具として手になじんでいるはずです。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?