量指定子で繰り返しを表す
直前のパターンが何回繰り返されるかを表すのが「量指定子」です。* は0回以上、+ は1回以上、? は0回または1回(あってもなくてもよい)を意味します。回数を細かく指定したいときは {n} でちょうどn回、{n,} でn回以上、{n,m} でn〜m回と書けます。例えば ab* は a の後に b が0個以上、go+gle は o が1個以上、colou?r は color と colour の両方にマッチします。+ ? { } は基本の正規表現では \ が必要になる点に注意します。
ここまでで、1文字を指す道具(. や文字クラス)を学んだ。しかし実際のパターンを書こうとすると、すぐ壁にぶつかる。「直前の文字が何回くり返されるか」を言いたい場面が、ほとんどなのだ。
数字が何桁続くか分からない、空白が1個か複数か分からない、ある文字があってもなくてもよい。こうした「回数」を表すのが量指定子だ。
量指定子は単独では使えない。必ず直前のパターン(1文字や文字クラス、グループ)に対して「それを何回くり返すか」を指定する形で働く。
1文字を指す道具と回数を指す道具を組み合わせることで、正規表現は一気に表現力を増す。回数を表す記号から見ていこう。
🔢 0以上・1以上・0か1――この差を取り違えると、何が起きるか
もっとも基本的な量指定子は * + ? の3つだ。
* は0回以上のくり返しを表し、「あってもなくてもよく、何個でもよい」という最も緩い指定だ。+ は1回以上、つまり「最低1個は必要で、何個でもよい」を表す。? は0回または1回、すなわち「あってもなくてもよいが、多くても1個」を意味する。
違いを具体例で見よう。ab* は a の後ろに b が0個以上なので a, ab, abb… に一致する。
go+gle は o が1個以上必要なので gogle, google, gooogle に一致するが、o が無い ggle には当たらない。
colou?r は u が0個か1個なので、color と colour の両方にちょうど一致する。
この3つの違い(0以上・1以上・0か1)を取り違えると、拾いたいものを取りこぼしたり、余計なものを拾ったりする。最初に丁寧に区別しておこう。
🎚 ちょうど何回、と厳密に言いたいときは
* + ? は便利だが、「ちょうど4回」「2回以上」といった厳密な回数は表せない。「ちょうど何回」「何回以上」「何回から何回まで」と指定したいときは、波かっこ { } を使う。
{n} はちょうどn回、{n,} はn回以上、{n,m} はn回からm回までを表す。
たとえば [0-9]{4} は「数字がちょうど4個」で、西暦の年号やコードの桁数チェックに使える。echo '西暦2026年と12月' から4桁を拾う例なら、一致するのは2026だけで、2桁の12は当たらない。
[0-9]{2,4} なら2個から4個、[0-9]{2,} なら2個以上だ。a{3} は aaa にちょうど一致する。回数が決まっている入力の検証では、この波かっこが正確で読みやすい武器になる。
じつは * + ? は波かっこで言い換えることもできる。* は {0,}、+ は {1,}、? は {0,1} と同じ意味だ。
こう並べてみると、3つの基本記号がそれぞれ「回数のよくある型」に名前を付けたものだと分かり、量指定子全体を一つの考え方として整理できる。
🌗 同じ式なのに、なぜ環境で当たったり当たらなかったりするのか
ここで一つ、つまずきやすい落とし穴がある。+ ? { } は、grep や sed が既定で使う基本正規表現(BRE)では、そのままだと特別な意味を持たず、ただの文字として扱われるのだ。
BRE でこれらを量指定子として使うには、直前にバックスラッシュを付けて \+ \? \{ \} と書く必要がある。
たとえば BRE で「a が1回以上」を表すには echo 'aaa' | grep -o 'a\+' と書く。grep -o 'a+' と書くと + はただの文字になって「a に続くプラス記号」を探してしまい、何も一致しない。
一方、後の項目で扱う拡張正規表現(grep -E)では、これらをエスケープなしで a+ や a{2,4} と書ける。
* . ^ $ [ ] の基本記号はどちらの方言でも同じなので、まずはそこを共通の土台と考えてよいだろう。「同じ式が動かない」と感じたら、いま自分がどちらの方言にいるかを疑うのが先決だ。
🍴 sedの置換で、なぜ思ったより広く消えるのか
もう一つ知っておきたいのが、* や + は既定で「できるだけ長く」一致しようとする性質だ。これを貪欲(greedy)マッチと呼ぶ。
たとえば <a>X</a><b>Y</b> という文字列に <.*> を当てると、最初の < から最後の > まで丸ごと一致してしまい、タグ1個だけをうまく取り出せない。
狙った範囲で止めたいときは、<[^>]*> のように「> 以外をくり返す」と書いて、次の区切りで自然に止まるようにするのが定番のテクニックだ。
この「区切り文字以外をくり返す」という発想は、カンマ区切りの1項目を取るときの [^,]* など、さまざまな場面で応用が効く。
なお PCRE(grep -P)が使える環境なら .*? という非貪欲指定で「できるだけ短く」一致させることもできるが、移植性は落ちる。
sed の置換で「思ったより広い範囲が消えた・置き換わった」というときは、まずこの貪欲マッチを疑うと原因にたどり着ける。
⚠ つまずいたら、まずこれ
失敗として多いのは、+ と * の混同だ。たとえば空白で区切られた値を扱うとき、区切りを ' *'(空白0個以上)と書くと空白が無くても一致してしまい、' +'(空白1個以上)と意図が変わる。
また BRE と ERE で \ の要否が逆になることを忘れ、grep 'a{2}' のように書いて当たらない、というのも頻出のつまずきだ。
もう一つ見落としやすいのが、量指定子は「直前の1つ」にしか効かないという原則だ。abc+ は「abc のくり返し」ではなく「ab に続いて c が1個以上」を意味し、まとまり全体をくり返したいなら (abc)+ のようにグループ化が要る。
実務では、量指定子は入力の形式検証(年号やコードの桁数チェック)、ログからの抽出(連続する数字や英字の並びを取る)、空白の正規化(連続する空白を1つにまとめる置換)など、至るところで使う正規表現の心臓部だ。
たとえば「行末の余分な空白を消す」なら sed -E 's/ +$//'、「連続する空白を1つにまとめる」なら sed -E 's/ +/ /g' のように、量指定子が置換の対象を柔軟に表現してくれる。
「* は0以上、+ は1以上、? は0か1、{n,m} で回数指定、BRE では \ が要る」。この対応を押さえておけば、狙ったくり返しを過不足なく書けるようになる。