正規表現の基本と、ECMAScript(JavaScript)における利用方法を紹介する連載。今回は、正規表現パターンの書き方の基本、代表的な文字クラス、文字集合、数量詞、そしてエスケープについて。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
前回では、ECMAScriptにおける正規表現に関係したクラスやメソッドを紹介しました。そこでは、幾つかの正規表現パターンを例として用いましたが、パターンがどのようなものかについては触れませんでした。ここでは改めて正規表現パターンの基本についてまとめておくことにしましょう。
ECMAScriptにおける正規表現パターンの基本は、正規表現リテラルです。正規表現リテラルは、以下のような形をしています。
/パターン文字列/フラグ
パターン文字列を、スラッシュ(/)で挟みます。この形を見つけると、ECMAScriptは、それが正規表現リテラルであると認識します。フラグは、パターン文字列に対するオプションです(省略可能)。前回の例で度々登場した、gフラグがその代表です。このように、フラグは半角の英文字で表され、多くの場合他のフラグと組み合わせて使うことができます。例えば、以下は極めてシンプルな正規表現リテラルの例です。
const regexp = /[a-c]/g;
正規表現リテラルを変数の初期値に与えるか、RegExpクラスのコンストラクタの引数に与えると、正規表現のオブジェクトを生成できます。正規表現オブジェクトが持つメソッドを用いるか、Stringクラスのメソッドに引数として与えることで、正規表現によるさまざまな検索操作が可能になります。
続けて、パターン文字列の書き方について紹介していきます。パターン文字列には、リテラルとメタ文字が登場します。リテラルとは、次に紹介するメタ文字以外の文字のことです(リテラルとは、「見たまま」という意味です)。例えばabcならabcと、空白を含めて文字の並びが一致するかを調べるときなどに使います。下記の例はリテラルのみからなるパターン文字列です。
const regexp = /England/g;
正規表現においてリテラルのみを使うことはほとんどありません。なぜなら、正規表現を使わないなら、通常の検索や置換を行うsearch()メソッド、replace()メソッド、contains()メソッド、includes()メソッドを使用すればいいからです。リテラルを、次に紹介するメタ文字と組み合わせて使用することで、正規表現ならではの複雑なマッチングが可能になります。
メタ文字とは、正規表現において特別な役割を持った文字のことです。そのまま、特殊文字ともいいます。特殊文字は、多くの場合はドット(.)などの半角記号であり、場合によっては複数の文字が組み合わさって機能します。その役割に応じて幾つかのカテゴリに分けられます。
このうち、文字クラスと文字集合、数量詞の一部についてはこの回で、その他については次回から詳しく紹介していきます。
Unicodeプロパティとは、Unicodeにおける各文字について、その文字がどのようなものであるかを示す属性です。例えば、ひらがなの「あ」にはひらがなを意味する「Hiragama」というプロパティが付いています。逆にいえば、Hiraganaというプロパティを指定すれば、あらゆるひらがなが該当することになります。詳細は、文字クラスを掘り下げる回にて紹介します。
正規表現で最も利用頻度が高いと思われるのが文字クラスです。文字クラスとは、包括的に文字を指定するためのメタ文字です。単体で使うよりは、後述する「数量詞」と組み合わせて使うことが多くなっています。ここでは、代表的な文字クラスを紹介し、次回以降の回で改めて掘り下げます。
メタ文字 | 機能 |
---|---|
. | あらゆる1文字とマッチする |
\d | 数字とマッチする |
\D | 数字以外とマッチする |
\w | 英数字とマッチする |
\W | 英数字以外とマッチする |
\s | ホワイトスペース文字とマッチする |
\S | ホワイトスペース文字以外とマッチする |
\t、\nなど | 制御文字にマッチする |
\xhh、\uhhhh | 指定する文字コードの文字にマッチする |
表1 文字クラス |
ドット(.)は、改行文字を除くあらゆる1文字とマッチします。非常に利用頻度の高いメタ文字ですが、適用範囲が広いので「ある文字の前後に任意の文字がある」という大まかな状況を指定するのに用いられることが多いようです。また、後述するアスタリスク(*)と組み合わせて「任意の文字列」を表すことにも用いられます。例えば下記「class_dot.js」のように、/.e/は'Hello, world'の'He'にはマッチしますが、'end!'にはマッチしません。当たり前ですが、/./は空でない文字列なら必ずマッチします。match()メソッドの結果はオブジェクト配列となります(以降も同様)。
const regex = /.e/; const str1 = 'Hello, world'; console.log(str1.match(regex)); // 「(1) ['He', index: 0, input: 'Hello, world', groups: undefined]」 const str2 = 'end!'; console.log(str2.match(regex)); // 「null」
なお、本連載のサンプルはGitHubで公開しています。下記URLからアクセスしてください。今回のサンプルは、basicsフォルダに配置されます。
\dは、あらゆる数字(ただし半角のアラビア数字のみ)にマッチします。具体的には、0〜9の文字が該当します。後述する「範囲」の中にある[0-9]と同じです。郵便番号や電話番号は数字の繰り返しが含まれますが、そのようなときに用いられることが多いです。例えば、下記「class_small_d.js」の'/\d/g'は'225−0002'の'2', '2', '5', '0', '0', '0', '2'にマッチします(ハイフン(-)にはマッチしない)。'California Number X'には全くマッチしません。
const regex = /\d/g; const str1 = '225−0002'; console.log(str1.match(regex)); // 「(7) ['2', '2', '5', '0', '0', '0', '2']」 const str2 = 'California Number X'; console.log(str2.match(regex)); // 「null」
\Dは、数字(ただし半角のアラビア数字のみ)以外のあらゆる文字にマッチします。つまり、\dの反対ということになります。
\wは、全ての半角英数字にマッチします。この半角英数字とは、0〜9とa〜zとA〜Zが該当します。さらに、半角記号の中でアンダースコア(_)だけが\wに含まれます。下記「class_small_w.js」では、@を含むメールアドレスのように見える箇所がマッチします。
const regex = /\w@\w/; const str1 = 'info@naosan.jp'; console.log(str1.match(regex)); // 「(1) ['o@n', index: 3, input: 'info@naosan.jp', groups: undefined]」 const str2 = 'https://naosan.jp/'; console.log(str2.match(regex)); // 「null」
\Wは、半角英数字以外の文字にマッチします。つまり、\wの反対ということになります。
\Sは、ホワイトスペースにマッチします。ホワイトスペースとは、主に空白文字、タブ文字、改ページ文字、改行文字などを指します。文字や文字列の前後をはじめとした特定の場所にスペースがあるかどうか調べる場合などに用いられます。なお、Unicode文字を含めるとホワイトスペースはさらに多くの文字が該当しますが、これについては文字クラスの回で改めて解説します。下記「class_small_s.js」では、1番目のmatch()メソッドでは両脇に空白文字がある数字がマッチします。2番目のmatch()メソッドではマッチするものがありません。
const regex = /\s\d\s/g; const str1 = '( 1 )回答を述べよ。'; console.log(str1.match(regex)); // 「(1) [' 1 ']」 const str2 = '(2)これが正解。'; console.log(str2.match(regex)); // 「null」
\Sは、ホワイトスペース以外の文字にマッチします。つまり、\sの反対ということになります。
続けて、文字クラスと同様に文字を包括的に指定するための文字集合を紹介します。
バーチカルバーあるいはパイプ(|)は、複数の項目のどれかがマッチすればよいというときに使用します。項目とは、リテラルや、他のメタ文字によって指定される文字群と思ってください(以降も同様)。例えば下記「range_pipe.js」では、URLにおけるスキーム部分を表すパターンとして、'http:|https:|ftp:'と表しています。これは'http:'か'https:'か'ftp:'のいずれかにマッチします。'mailto:'にはマッチしません。
const regexp = /http:|https:|ftp:/; const str1 = 'http://www.example.com'; console.log(str1.match(regexp)); // 「(1) ['http:', index: 0, input: 'http://www.example.com', groups: undefined]」 const str2 = 'https://sp.example.com'; console.log(str2.match(regexp)); // 「(1) ['https:', index: 0, input: 'https://sp.example.com', groups: undefined]」 const str3 = 'mailto:someone@example.com'; console.log(str3.match(regexp)); // 「null」
なお、ここではパイプ(|)のシンプルな例としているので、URLにおけるスキーム部分のマッチングとしては不完全です('xhttp:'などもマッチする)。完全な例は、境界やグループを取り上げる回にて改める予定です。
ブラケットあるいは大括弧([ ])は、リテラル、文字クラスなどをまとめた文字集合を指定するときに使用します。例えば'[0-9]'は0〜9の半角数字すなわち\dと同義となります。ここではハイフン(-)を指定したことで範囲を指定したことになりますが、このようにリテラルと文字クラスを並べて、それらによる文字集合を指定することができます。例えば、16進数に使われる文字集合を指定するなら、'[\da-fA-F]'となります。なお、'[^〜]'と先頭にハット(^)を付けることで、「それら以外」という否定の意味になります。下記「range_set.js」で実際に確かめてください。
const regex1 = /[\da-fA-F]/g; const str1 = '12ab'; console.log(str1.match(regex1)); // 「(4) ['1', '2', 'a', 'b']」 const str2 = '0x12ab'; console.log(str2.match(regex1)); // 「(5) ['0', '1', '2', 'a', 'b']」 const regex2 = /[^\da-fA-F]/g; console.log(str2.match(regex2)); // 「(1) ['x']」
続けて、文字クラスや文字集合と組み合わせて使うことが多い数量詞を紹介します。数量詞については次回以降で掘り下げますので、ここでは代表的なものに限定して紹介します。
アスタリスク(*)は、直前の項目の0回以上の繰り返しにマッチします。ドット(.)と組み合わせて、あらゆる文字列を表すパターンとして使うことが多い数量詞です。例えば下記「quantifier_asterisk.js」の/(.*)/は、全角パーレンに挟まれたあらゆる文字列にマッチします。
const regex = /(.*)/; const str1 = 'これを正規表現(Regular Expression)という。'; console.log(str1.match(regex)); // 「(1) ['(Regular Expression)', index: 7, input: 'これを正規表現(Regular Expression)という。', groups: undefined]」 const str2 = 'これを正規表現〈Regular Expression〉という。'; console.log(str2.match(regex)); // 「null」
ここでは、「.*」の前後にリテラルを指定したのでこのような結果が得られていますが、「.*」を単独でパターンに指定した場合には、必ず空文字列にマッチすることに注意してください。これは、「.*」が0文字以上の文字列を表すからで、どのような文字列でも0文字以上の文字列を含むと解釈されます。
プラス(+)は、直前の項目の1回以上の繰り返しにマッチします。会員番号など、1文字以上の省略できない数字が確かにそこにあるかどうか調べるときなどに用いられます。例えば下記「quantifier_plus.js」の/\d+/は、"123445567"の全てにマッチしますが、"Undefined"にはマッチしません。*と異なり、数字は1文字は存在していなければなりません。つまり、1文字以上の数字の繰り返しという指定になります。
const regex = /\d+/; const str1 = '123445567'; console.log(str1.match(regex)); // 「(1) ['123445567', index: 0, input: '123445567', groups: undefined]」 const str2 = 'Undefined'; console.log(str2.match(regex)); // 「null」
疑問符(?)は、直前の項目の0回か1回の出現にマッチします。省略可能な文字があるとき、それの有無にかかわらずマッチさせたいというときに使用できます。例えば下記「quantifier_question.js」は、「http:」と「https:」のいずれにもマッチしますが、「httpx:」にはマッチしません。
const regex = /https?:/; const str1 = 'https://naosan.jp/'; console.log(str1.match(regex)); // 「(1) ['https:', index: 0, input: 'https://naosan.jp/', groups: undefined]」 const str2 = 'http://naosan.jp'; console.log(str2.match(regex)); // 「(1) ['http:', index: 0, input: 'http://naosan.jp', groups: undefined]」
中括弧({ })で整数値を囲んだ'{n}'や'{n,}'、'{n,m}'は、直前の項目の指定回数の出現にマッチします。'{n}'はn回、'{n,}'はn回以上、'{n,m}'はn回以上m回以下となります。郵便番号や電話番号のように決まった数で繰り返す文字があるとき、それが正しい繰り返し数になっているかどうかをチェックしたいときに使用できます。例えば下記「quantifier_appearance.js」の/\d{3}-\d{4}/は、"225-0002"のみマッチして、"2250002"や"225-02"にはマッチしません。
const regex = /\d{3}-\d{4}/; const str1 = '225-0002'; console.log(str1.match(regex)); // 「(1) ['225-0002', index: 0, input: '225-0002', groups: undefined]」 const str2 = '2250002'; console.log(str2.match(regex)); // 「 null」 const str3 = '225-02'; console.log(str3.match(regex)); // 「 null」
ここまでは、数量詞の代表としてアスタリスク、プラス、疑問符などを紹介し、リテラルや文字クラスに対して使用した例を示しました。サンプルの正規表現パターンをいろいろ変えて、動きがどう変わるか試してみてください。
メタ文字も文字なので、その役割とは関係なく単純な文字として扱いたい、すなわちリテラルとして扱いたい場合があります。このようなときにはエスケープを用います。メタ文字は基本的に記号で始まりますので、その記号の前にバックスラッシュ(\)を付加します。
下記「escape.js」は、メタ文字の一つであるドット(.)をリテラルとして扱うように指定しています。1番目のmatch()メソッドでは、'filename.txt'に'.txt'が含まれるのでマッチしますが、2番目のmatch()メソッドでは、'filename*txt'に'.txt'は含まれないのでマッチしません。これから、メタ文字としてのドット(.)が単なるリテラルとして扱われていることが分かります。
const regex = /\.txt/; // ドットをエスケープ const str1 = 'filename.txt'; console.log(str1.match(regex)); // 「(1) ['.txt', index: 8, input: 'filename.txt', groups: undefined]」 const str2 = 'filename*txt'; console.log(str2.match(regex)); // 「null」
エスケープは、リテラルに対しても使うことができます。この場合は、例えば\sというメタ文字として機能することになります。この代表が文字クラスです。
このようにバックスラッシュ(\)は文字として非常に重要なのですが、バックスラッシュ自身をリテラルとして扱いたい場合には、バックスラッシュを2個連ねて'\\'というように記述します。
この回では、ECMAScriptにおける正規表現の実践の初回として、正規表現パターンの書き方の基本、そして利用頻度の高い文字クラス、文字集合、数量詞について紹介しました。
次回は、マッチしたパターンをグループ化したり、別の文字列に置き換えたりする方法について紹介します。
WINGSプロジェクト
有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティー(代表山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手掛ける。2021年10月時点での登録メンバーは55人で、現在も執筆メンバーを募集中。興味のある方は、どしどし応募頂きたい。著書、記事多数。
・サーバーサイド技術の学び舎 - WINGS(https://wings.msn.to/)
・RSS(https://wings.msn.to/contents/rss.php)
・Twitter: @yyamada(https://twitter.com/yyamada)
・Facebook(https://www.facebook.com/WINGSProject)
Copyright © ITmedia, Inc. All Rights Reserved.