最終更新:
正規表現で文字数を指定・チェックする方法 - 量指定子 {n,m} の実践
正規表現で文字数を指定するには、量指定子 {n,m} を使います。「8 文字以上 20 文字以下」なら ^.{8,20}$ が基本形です。ただし「. が何を 1 文字と数えるか」は言語やフラグによって変わり、絵文字や一部の漢字では見た目の文字数とズレます。この記事では、そのまま使える文字数チェックのパターン集と、JavaScript・Python での書き方、絵文字・サロゲートペアの罠、内容チェックと組み合わせる先読みのテクニックを、実際に動作確認したコード例つきで解説します。
文字数を指定する量指定子の基本
| 書き方 | 意味 | 例 |
|---|---|---|
{n} | ちょうど n 回 | ^[0-9]{4}$ = 数字ちょうど 4 桁 |
{n,} | n 回以上 | ^.{8,}$ = 8 文字以上 |
{n,m} | n 回以上 m 回以下 | ^.{8,20}$ = 8〜20 文字 |
? | 0 回か 1 回 (= {0,1}) | ^-?[0-9]+$ = 符号は任意 |
+ | 1 回以上 (= {1,}) | ^[a-z]+$ = 小文字 1 文字以上 |
* | 0 回以上 (= {0,}) | ^[a-z]*$ = 空文字も許可 |
文字数チェックで重要なのは、パターン全体を ^ (先頭) と $ (末尾) で挟むことです。アンカーを付けない .{8,20} は「どこかに 8〜20 文字の並びがある」という意味になり、100 文字の文字列にも一致してしまいます。「文字列全体が 8〜20 文字」を表すには ^.{8,20}$ と書きます。
そのまま使える文字数チェックのレシピ集
フォームのバリデーションでよく使うパターンをまとめました。いずれも Node.js 26 と Python 3.9 で動作確認済みです。
| 用途 | 正規表現 | 補足 |
|---|---|---|
| 8〜20 文字 (種類問わず) | ^.{8,20}$ | 改行を含む場合は後述の注意点あり |
| 140 文字以内 | ^.{0,140}$ | 空文字も許可。1 文字以上なら {1,140} |
| 数字ちょうど 4 桁 | ^[0-9]{4}$ | 暗証番号・郵便番号の前半など |
| 英数字 8 文字以上 | ^[a-zA-Z0-9]{8,}$ | 記号を含めない ID など |
| ひらがなのみ 1〜10 文字 | ^[ぁ-んー]{1,10}$ | 「ふりがな」欄。長音「ー」を含む |
| カタカナのみ 1〜20 文字 | ^[ァ-ヶー]{1,20}$ | 「フリガナ」欄。「ヴ」「ヶ」を含む |
| 全角文字のみ 1〜5 文字 | ^[^\x00-\x7F]{1,5}$ | ASCII 以外を全角とみなす簡易判定 |
ひらがな・カタカナの範囲指定は、フォームのふりがな欄の定番です。ただし [ぁ-ん] に長音記号「ー」は含まれないため、「ラーメン」のような語を通すには表のように ー を明示的に足す必要があります。この足し忘れは実務で最も多いつまずきポイントです。
「.」は何を 1 文字と数えるか - 絵文字とサロゲートペアの罠
正規表現の文字数指定が想定とズレる最大の原因は、. や {n,m} が数える単位です。JavaScript の正規表現は、既定では UTF-16 のコードユニット単位で数えます。以下は実際の挙動です (Node.js 26 で検証)。
// 「𠮷」(つちよし) はサロゲートペア = UTF-16 で 2 コードユニット
"𠮷".length; // 2
/^.{2}$/.test("𠮷"); // true (u フラグなし: 2 文字扱い)
/^.$/u.test("𠮷"); // true (u フラグあり: 1 文字扱い)
// 家族の絵文字は 7 コードポイント (4 絵文字 + 3 つの ZWJ)
"👨👩👧👦".length; // 11 (コードユニット)
/^.{7}$/u.test("👨👩👧👦"); // true (u フラグ: コードポイント単位)
つまり同じ {8,20} でも、数える単位は 3 段階あります。
| 数える単位 | JavaScript での書き方 | 「𠮷」 | 「👨👩👧👦」 |
|---|---|---|---|
| UTF-16 コードユニット | /^.{n,m}$/ (フラグなし) | 2 | 11 |
| コードポイント | /^.{n,m}$/u (u フラグ) | 1 | 7 |
| 書記素クラスタ (見た目の 1 文字) | 正規表現では不可。Intl.Segmenter を使う | 1 | 1 |
「見た目の文字数」で数えたい場合、正規表現の量指定子だけでは実現できません。JavaScript なら Intl.Segmenter で書記素単位に分割してから件数を数えます。一方 Python 3 の文字列はコードポイント単位なので、re.fullmatch(r".", "𠮷") は最初から一致します (検証済み)。それでも家族の絵文字は 7 コードポイントのままなので、絵文字を「1 文字」と数えたい要件では Python でも書記素分割ライブラリが必要です。絵文字のカウントの仕組みは絵文字は何文字としてカウントされるのかで詳しく解説しています。
改行を含むテキストの落とし穴
もう 1 つの定番の罠が改行です。. は既定では改行に一致しないため、複数行のテキストは文字数以前に不一致になります (Node.js・Python とも検証済み)。
/^.{1,10}$/.test("ab\ncd"); // false (改行で不一致)
/^[\s\S]{1,10}$/.test("ab\ncd"); // true (改行も数える書き方)
/^.{1,10}$/s.test("ab\ncd"); // true (s フラグで . を改行にも一致させる)
Python では re.DOTALL フラグが同じ役割を持ちます。テキストエリアのように改行が入り得る入力を ^.{1,500}$ で検証すると、改行入りの正常な入力をすべて弾いてしまいます。複数行を許可する文字数チェックには ^[\s\S]{1,500}$ (JavaScript) や re.fullmatch(r".{1,500}", text, re.DOTALL) (Python) を使いましょう。
先読みで「文字数 + 内容」を同時にチェックする
パスワードのように「8〜20 文字、かつ大文字と数字を含む」といった複合条件は、先読み (lookahead) と量指定子の組み合わせで 1 本の正規表現にまとめられます。
/^(?=.*[A-Z])(?=.*[0-9])[A-Za-z0-9]{8,20}$/
// "Passw0rd" → true (8 文字・大文字と数字あり)
// "password" → false (大文字と数字がない)
先読み (?=...) は位置だけを検査して文字を消費しないため、「大文字を含む」「数字を含む」という条件を好きなだけ重ねた上で、最後の [A-Za-z0-9]{8,20} が文字種と文字数をまとめて確定します。ただし条件を増やすほど可読性は落ちるので、実務では正規表現 1 本に詰め込まず、文字数チェックと内容チェックをコード上で分けた方が保守しやすい場面も多くあります。正規表現自体をどこまで複雑にしてよいかという設計論は正規表現の文字数と設計で扱っています。
フォームの maxlength・データベースとの整合
正規表現の文字数チェックは、単体では完結しません。フロントエンドの maxlength 属性、サーバ側の正規表現、データベースの列定義の 3 か所で「数える単位」が揃っていないと、どこか 1 か所だけ通ってしまう不整合が起きます。HTML の maxlength は UTF-16 コードユニット単位、MySQL の VARCHAR(n) は文字単位 (ただしUTF-8 のバイト数制約が別にある)、と単位はバラバラです。絵文字入りの入力で「フォームは通るのに DB でエラー」が起きるのはこの不一致が原因です。詳しくは VARCHAR の文字数設計とフォーム入力の文字数バリデーションを参照してください。
正規表現を書く前に「そもそもこのテキストは何文字なのか」を確かめたいときは、文字数カウントスが便利です。書記素単位の文字数に加えて、バイト数や文字種の内訳もリアルタイムに表示されるため、テスト文字列の作成やバリデーション境界値の確認に使えます。
正規表現の学習書は Amazon でも探せます。
よくある質問
- 正規表現で「8 文字以上 20 文字以下」はどう書きますか?
- ^.{8,20}$ と書きます。パターン全体を ^ と $ で挟むのがポイントで、アンカーのない .{8,20} は「どこかに 8〜20 文字の並びがある」という意味になり、上限のチェックとして機能しません。
- 正規表現の文字数チェックで絵文字がズレるのはなぜですか?
- JavaScript の正規表現は既定で UTF-16 コードユニット単位で数えるためです。「𠮷」のようなサロゲートペアの文字や絵文字は 2 単位以上を消費します。u フラグを付けるとコードポイント単位になりますが、家族の絵文字のような結合絵文字はそれでも複数文字扱いです。
- 改行を含むテキストを正規表現で文字数チェックするには?
- 「.」は既定で改行に一致しないため、JavaScript では ^[\s\S]{1,500}$ のような書き方か s フラグ、Python では re.DOTALL フラグを使います。テキストエリアの入力を ^.{1,500}$ で検証すると、改行入りの正常な入力をすべて弾いてしまいます。