In 'n' Out

知識を取り込み、そして発信する

正規表現

正規表現とは何か

正規表現とは、文字列の中から特定のパターンを探したり、条件に一致する文字列かどうかを判定したりするための記述方法です。
単なる文字の一致だけでなく、文字の種類や並び方、位置や回数といった条件をまとめて表現できるため、入力チェックや検索、置換処理など幅広い場面で利用されます。
この記事では、正規表現の基本構造と、よく使われる代表的な記号を段階的に解説します。

正規表現の第一歩

正規表現は、文字列がある条件に合っているかを調べるための仕組みです。
JavaScriptでは、正規表現に対してtest()を使うことで、真偽値で判定を行います。

構文

/条件/.test(文字列);

この1行で文字列が条件に一致しているかを判定できます。
詳しく見ていきましょう。
まず、/条件/のスラッシュで囲まれた条件が正規表現です。
ここにどんな文字を許可するかという条件を書きます。
続くtest(文字列)は、正規表現と文字列を照合するための処理です。
イメージ図で見てみましょう。

正規表現・test()のイメージ図
test()は、条件と文字列を照合するための処理です。

記述は少し独特ですが、処理の流れ自体はシンプルです。
では、実際に何を照合するのか見ていきましょう。

文字クラス

文字クラスは、複数の文字の中からいずれか1文字に一致させたい場合に使用します。

構文

/[一致させたい文字]/

ブラケット[]の中に指定した文字のうち、どれか1文字に一致します。
順番は関係なく、含まれているかどうかのみが判定基準になります。

コード例

//literals1.js
const value = 'cde';

if (/[abc]/.test(value)) {
    alert("含まれています");
} else {
    alert("含まれていません");
}

動作例

※このボタンは動作確認用です

/[abc]/の正規表現は、aまたはbまたはcのいずれか1文字に一致します。
照合したい文字列はvalueに代入したcdeで、cが該当するためalertには含まれていますと表示されます。
次に文字列の位置を指定する場合はどうすればいいでしょうか。

構文

/^一致させたい先頭文字/
/一致させたい末尾文字$/

まず最初の構文には条件の前に^がついています。
これは文字列の先頭を表しています。
次の構文には条件の後に$がついています。
これは文字列の末尾を表しています。

コード例

//literals2.js
const value = 'ade';

if (/^a/.test(value)) {
    alert("aから始まります");
} else {
    alert("aから始まりません");
}
       
if (/d$/.test(value)){
    alert("dで終わっています");
} else {
    alert("dで終わっていません");
}

動作例

※このボタンは動作確認用です

/^a/の正規表現は、照合する文字列がaから始まっているかを判別します。
照合したい文字列はvalueに代入したadeで、aから始まっているため、alertにはaから始まりますと表示されます。
一方で/d$/の正規表現は、照合する文字列がdで終わっているかを判別します。
照合したい文字列の終わりはeなので、dで終わっていないため、alertにはdで終わっていませんと表示されます。
^と$を同時に使うとどうなるでしょうか。

コード例

//literals3.js
let value = 'abcd';

if (/^abcd$/.test(value)) {
    alert("完全に一致しています");
} else {
    alert("一致しません");
}

value = 'acbd';

if (/^abcd$/.test(value)) {
    alert("完全に一致しています");
} else {
    alert("一致しません");
}

動作例

※このボタンは動作確認用です

まず、valueにabcdを代入して、最初のif文で^abcd$とvalueのabcdを照合します。
順番も文字もすべて同じため、alertで完全に一致していますと表示されます。
次にvalueにacbdを代入して、次のif文で同じように照合すると、先頭と末尾の文字は同じですが、順番が異なるため、alertで一致しませんと表示されます。
^は文字列の先頭、$は文字列の末尾を表すと説明しましたが、^と$で囲むことで、囲まれた内容が完全に一致しているかを判定する役割になります。
正規表現では、このように記号を組み合わせることで判定の意味が変わることがあります。

では、なぜ最初の構文では[]を使い、^や$では[]を使わなかったのでしょうか。
なお、[^一致させたい文字]とすると否定という意味になりますが、意味が変わるため、後ほど説明します。
今は理解しなくてもかまいません。
理解しておくべきことは次の3つです。
[一致させたい文字]は文字の集合を表し、その中のどれか1文字に一致するかを判定する
/^一致させたい文字/や/一致させたい文字$/は、文字そのものではなく、文字列の先頭や末尾といった位置を表す
/^一致させたい文字$/は、先頭と末尾の位置指定を組み合わせることで、文字列全体が完全に一致しているかを判定する
この3つだけ、しっかり理解しておきましょう。

任意の1文字

.は任意の1文字を表します。
英数字でも記号でも、何か1文字あれば一致します。

構文

/./

1文字以上あるかどうかを調べるときによく使われます。

コード例

//literals4.js
const value = 'a';

if (/./.test(value)) {
    alert("1文字以上あります");
} else {
    alert("空文字です");
}

動作例

※このボタンは動作確認用です

valueにはaと文字が代入されているため、1文字以上ある状態です。
そのためalertには1文字以上ありますと表示されます。
では、空文字と比較してみましょう。

コード例

//literals5.js
const value = '';

if (/./.test(value)) {
    alert("1文字以上あります");
} else {
    alert("空文字です");
}

動作例

※このボタンは動作確認用です

valueは空文字なので、1文字も存在しません。
そのため/./は一致せず、alertには空文字ですと表示されます。
/[一致させたい文字]/と何が違うのでしょうか?
それは[]内のどれか1文字が含まれているか、.は種類を問わず何か1文字が含まれているかの違いで、つまり.は条件を指定しない[]のようなものと考えてください。
ただし、厳密には改行は含まれない点が異なります。 では、1文字ではなく、2文字以上あるかを判定したい場合はどうすればいいでしょうか。

量指定子

量指定子とは繰り返しを意味します。
正規表現では、直前の文字や条件に対して何回繰り返すかを指定できます。
なお、繰り返しはいくつか存在しますが、よく使うもののみ説明します。

構文

/一致させたい文字+/
/一致させたい文字*/
/一致させたい文字?/

+は直前に書かれた文字や文字クラスが、最低1回以上連続して出現する場合に一致します。
*は直前の条件が0回以上繰り返されることを表します。
?は直前の条件が0回または1回だけ出現することを表します。

コード例

//literals6.js
const value = 'abc';

if (/.+/.test(value)) {
    alert("1文字以上あります");
} else {
    alert("空文字です");
}

動作例

※このボタンは動作確認用です

+を使っていますが分解して説明します。
まず.は任意の1文字で、+は1回以上という意味です。
.+は任意の文字が1文字以上あるか判定します。
valueはabcで3文字あるため一致し、alertには1文字以上ありますと表示されます。

コード例

//literals7.js
const value = '';

if (/.*/.test(value)) {
    alert("常に一致します");
}

動作例

※このボタンは動作確認用です

*を使っていますが分解して説明します。
まず.は任意の1文字で、*は0回以上という意味です。
.*は任意の文字がなくてもよいと判定します。
そのため、valueが空文字でも、alertには常に一致しますと表示されます。
ただし、入力チェックには向かない場合があります。

コード例

//literals8.js
const value = 'a';
const empty = '';

if (/a?/.test(value)) {
    alert("一致します");
}

if (/a?/.test(empty)) {
    alert("一致します");
}

動作例

※このボタンは動作確認用です

?は0回または1回という意味なので、alertはaがあってもなくても一致します。
実際、valueにはaが入っているため、emptyは空文字でも一致します。
存在してもしなくてもいい文字を表すときに使います。

では、これらはどうやって使えばいいのか?
まず、条件を設計しながら組み立てます。
先頭に#があってもなくてもよい→#?
その後にgから始まる1文字以上の文字が続く→#?g.+
末尾に!が 0文字以上ついてもよい→#?g.+!
文字列全体を判定する→/^#?g.+!$/
以上の条件を設定したコード例を見てみましょう。

コード例

//literals9.js
const value = ['#good morning!!', '#hello!!', 'good evening!', '#good night'];

for (let i = 0; i < value.length; i++) {
    if (/^#?g.+!*$/.test(value[i])) {
        alert("条件に一致しています");
    } else {
        alert("条件に一致していません");
    }
}

動作例

※このボタンは動作確認用です

まず、#good morning!!は、先頭に#があってもよく、gで始まり、その後に文字が続き、末尾に!があってもよいので、条件に一致します。
次に、#hello!!先頭に#はありますが、gで始まっていないため条件に一致しません。
続いて、good evening!は先頭に#はなくてもよく、gで始まり、その後に文字が続き、末尾に!があってもよいので、条件に一致します。
最後に、#good nightは先頭に#があってもよく、gで始まり、その後に文字が続き、末尾に!はなくてもいいので、条件に一致します。
このように、正規表現の各部分がどの文字列に当てはまるかを順に見ていくと、どの文字列が一致するかを直感的に理解できます。
また、正規表現は左から右へと評価されるため、#hello!!はgから始まっていない時点で、それより後の条件は評価されず不一致となります。

半角・全角文字

日本語には半角文字・全角文字という概念があります。
日頃、会員登録やアンケートの場面で全角文字で入力してください、半角数字で入力してくださいという場面に遭遇すると思います。
正規表現では半角・全角文字のチェックも可能です。

構文

[0-9]
[0-9]
[A-Za-z]
[A-Za-z]
[ぁ-ん]
[ァ-ヶ]
[ヲ-゚]
\d
\D

[0-9]は半角数字、[0-9]は全角数字、[A-Za-z]は半角英字、[A-Za-z]は全角英字で、入力チェックでも最も使用頻度が高い文字種です。
[ぁ-ん]は平仮名、[ァ-ヶ]は全角カタカナ、[ヲ-゚]は半角カタカナで、英数に次ぎ使用頻度が高い文字種です。
\dは[0-9]の省略形で半角数字、\Dは半角数字以外となります。
\sは半角スペース、\u3000は全角スペースとなります。
文字クラスを使うことで、許可したい文字種をまとめて指定でき、英数に関しては順番通りでよいため覚えやすいと思います。
一方で平仮名、全角カタカナ、半角カタカナは文字の配置を基に指定するため、この形を覚えなくてはなりません。

コード例

//literals10.js
let value = prompt('文字を入力してください');

if (/^[0-9]+$/.test(value)) {
    alert("半角数字です");
} else if (/^[A-Za-z]+$/.test(value)) {
    alert("半角英字です");
} else {
    alert('半角英数以外です');
}

動作例

※このボタンは動作確認用です

このコード例ではpromptに入力した文字を正規表現で判別しています。
最初に、^[0-9]+$で文字列すべてが半角数字かどうか判別し、半角数字のみだとalertに半角数字ですと表示されます。
次に、半角数字ではなかったら半角英字かどうか判別し、半角英字のみだとalertに半角英字ですと表示されます。
最後に半角数字でも半角英字でもなかったら、半角英数以外ですと表示されます。
このように入力した文字列が、大きくルールに則っているかの判別も可能です。
ひらがなやカタカナも同じように指定できます。
それでは[\d]と[\D]のコード例を見てみましょう。

コード例

//literals11.js
let value1 = prompt('名前を入力してください');

if (/^[\D]+$/.test(value1)) {
    alert('名前は' + value1 + 'です');
} else {
    alert(value1 + 'には数字が含まれているため、名前ではありません');
}

let value2 = prompt('年齢を入力してください');

if (/^[\d]+$/.test(value2)) {
    alert(value2 + '歳です');
} else {
    alert(value2 + 'には数字以外が含まれているため、年齢ではありません');
}

動作例

※このボタンは動作確認用です

このコード例ではpromptに2回入力を求められます。
名前には通常、数字は含まれません。
そのため、^[\D]+$で入力した文字列に数字が入っていないかどうか判別し、数字以外であればalertに名前が表示されます。
次に、年齢には数字以外は入ることはありません。
そのため、^[\d]+$で入力した文字列に数字以外が入っていないかどうか判別し、数字のみであればalertに年齢が表示されます。
また、半角数字を判別するには、[0-9]よりも、\dの方が実務ではよく使用されます。

半角・全角記号

文字には記号を使う場合があります。
例えばジュースや、野菜・果物などの長音や中黒、姓名の間の空白(スペース)などです。
丸や四角といった単なる装飾記号とは異なり、人名や物の名前に使われることがあるため、正確に判別する必要があります。
また、これらの記号は見た目では判断しにくく、Unicode番号で指定することで正確に扱うことができます。

構文

\s
\u3000
\u002D
\uFF0D
\u30FC
\u2015
\u2010
\u30FB
\uFF65
\u2022
\u25CF

ユニコード対応表は下記となります。

記号一覧
表示記号ユニコード内容
「 」\s半角空白(スペース)
「 」\u3000全角空白(スペース)
-\u002D半角ハイフン
\uFF0D全角ハイフン
\u30FC長音
\u2015ダッシュ
\u2010ハイフン
\u30FB中黒
\uFF65半角中黒
\u2022ビュレット
\u25CF黒丸

黒丸は明らかに違うと思われるかもしれませんが、これらはフォントが異なると違った見え方になってしまうため、どれが使用されているかわからなくなります。
また、空白のかぎかっこは空白が入っているとわかるように使用したためで、実際にかぎかっこは付きません。

コード例

//literals12.js
const arr = ['ジョン・レノン', 'ポール•マッカートニ―', 'ジョ-ジ・ハリスン', 'リンゴ・スタ‐', 'ザ ビ‐トルズ'];

const normalized = arr.map(function (name) {
    name = name.split('\u30FB').join('・');
    name = name.split('\uFF65').join('・');
    name = name.split('\u2022').join('・');

    name = name.split(' ').join(' ');
    name = name.split('\u3000').join(' ');

    name = name.split('\u002D').join('ー');
    name = name.split('\uFF0D').join('ー');
    name = name.split('\u2010').join('ー');
    name = name.split('\u30FC').join('ー');
    name = name.split('\u2015').join('ー');

    return name.trim();
});

for (var i = 0; i < arr.length; i++) {
    alert("処理前: " + arr[i] + "\n処理後: " + normalized[i]);
}

動作例

※このボタンは動作確認用です

配列内の文字列には、半角・全角の空白、中黒、ハイフン、長音、ビュレットなどが混在していることがあります。
見た目だけでは判別できないため、Unicodeで指定して一律に置換するのが安全です。
このコードではmapを使って配列の各要素を順番に処理し、split().join()を使って対象文字をすべて置換しています。
split().join()を使うことで、複数の同じ文字も安全に置換でき、無限ループや意図しない部分置換のリスクを避けられます。
空白は全角、ハイフン系は全角ハイフン、中黒・ビュレットは全角中黒、長音・ダッシュは全角長音に統一しています。
正しい文字も含めて置換することで、条件分岐を省き、コードを簡潔に保ちながら配列内の文字列を統一できます。
ハイフンは文字列の途中でも数値や名前に使われるほか、正規表現の文字クラス [0-9-] では範囲指定の記号としても使われます。
範囲指定と混同しないように、正規表現でハイフンを扱う場合は位置やエスケープに注意してください。
配列内の文字列の統一置換では、Unicode指定でまとめて置換するのが安全です。

混在文字列の統一

配列内の文字列には、半角・全角の空白やハイフン、中黒、長音など、見た目が似ていても異なる文字が混在していることがあります。
そのままでは検索や置換、表示の際に意図しない挙動が起きることがあるため、統一して扱うことが重要です。
ここでは、こうした文字を安全に全角に統一する方法を解説します。

構文

//リテラル構文
変数 = /一致させたい文字列/フラグ(g・i・m);
//コンストラクタ構文
変数 = new RegExp(一致させたい文字列, フラグ(g・i・m));

正規表現を使う際にリテラル構文とコンストラクタ構文の二種類があります。
リテラル構文は正規表現リテラルを使って文字列内の特定の文字や文字列を検索するためのパターンを変数に代入します。
フラグは検索や置換の動作を指定します。
コンストラクタ構文は関数で、正規表現オブジェクトを作り、文字列の検索や置換に使うパターンを変数に代入します。
第一引数に一致させたい文字列を渡し、第二引数にフラグを渡すことで検索の動作を指定できます。
フラグのgは文字列全体にマッチさせる、iは大文字小文字を区別せずにマッチさせる、mは複数行文字列で行頭や行末を扱う動作になります。
この方法を使うと、文字列やUnicode文字を変数で動的に指定して正規表現を作ることができ、柔軟に検索や置換を行うことが可能です。
また、フラグは1つだけでなく複数の使用可能です。

コード例

//literals13.js
const arr = ['ジョン・レノン', 'ポール•マッカートニ―', 'ジョ-ジ・ハリスン', 'リンゴ・スタ‐', 'ザ ビ‐トルズ'];

const middleDot = /[\u30FB\uFF65\u2022]/g;
const space = /[\u3000]/g;
const hyphen = new RegExp('[\u002D\uFF0D\u2010]', 'g');
const longDash = new RegExp('[\u30FC\u2015]', 'g');

const normalized = arr.map(name => {
    name = name.replace(middleDot, '・');
    name = name.replace(space, ' ');
    name = name.replace(hyphen, 'ー');
    name = name.replace(longDash, 'ー');
    return name.trim();
});

for (let i = 0; i < arr.length; i++) {
    alert("処理前: " + arr[i] + "\n処理後: " + normalized[i]);
}

動作例

※このボタンは動作確認用です

このコードは全角・半角統一のコード例と同じ結果を得られます。
middleDotとspaceはリテラル構文で、hyphenとlongDashはRegExp関数で変数に代入しました。
いずれも置換したい文字を文字列すべてにマッチするようにしています。
このコードではmapを使って配列の各要素を順番に処理し、正規表現とreplaceを使って対象文字をすべて置換しています。
replaceにgフラグ付きの正規表現を使うことで、文字列中の複数の同じ文字も安全に置換でき、無限ループや意図しない部分置換のリスクを避けられます。
空白は全角、ハイフン系は全角ハイフン、中黒・ビュレットは全角中黒、長音・ダッシュは全角長音に統一しています。
正しい文字も含めて置換することで条件分岐を省き、コードを簡潔に保ちながら配列内の文字列を統一できます。

中括弧量指定子

中括弧量指定子{}は、直前の条件が出現する回数を数値で指定するための記号です。

構文

/一致させたい文字{回数}/
/一致させたい文字{最小, 最大}/
/一致させたい文字{最小,}/

{}の中に数値を書くことで、直前の文字や文字クラスの繰り返し回数を指定します。
1つの数値のみ指定した場合は、その回数ちょうどに一致します。
カンマを使うことで、回数の範囲指定や下限のみの指定も可能です。

コード例

//literals11.js
let zip = "123-4567";

if (/^\d{3}-\d{4}$/.test(zip)) {
    console.log(zip + " は正しい郵便番号です");
} else {
    console.log(zip + " は正しい郵便番号ではありません");
}

let phone = "09012345678";
if (/^\d{10,11}\/.test(phone)) {
    console.log(phone + " は正しい電話番号です");
} else {
    console.log(phone + " は正しい電話番号ではありません");
}

let password = "P@ssw0rd!";
if (/^[A-Za-z0-9!@#$%^&*]{8,}$/.test(password)) {
    console.log(password + " は有効なパスワードです");
} else {
    console.log(password + " は有効なパスワードではありません");
}

動作例

※このボタンは動作確認用です

まず、郵便番号は3桁とハイフンと4桁の並びで構成されています。
そのため、^\d{3}-\d{4}$で正規表現が使用でき、一致させることができます。
続いて、電話番号はハイフンなしで10桁か11桁で構成されています。
そのため、^\d{10,11}$で正規表現が使用でき、一致させることができます。
最後にパスワードは8文字以上で設定という条件の基で構成しました。
そのため、^[A-Za-z0-9!@#$%^&*]{8,}で正規表現が使用でき、一致させることができます。
このように入力チェックでは{}を使用して正規表現を使うことができます。

ただ、すべて覚えておくのは至難の業なので、よく使う正規表現を使いまわせるようフォーマット化しておけば、実際に使用する際にコピー&ペーストできるよう準備をしておくとよいでしょう。