「正規表現」とは――すぐに使う方法と「ECMAScript」のバージョン:ECMAScriptで学ぶ正規表現(1)
正規表現の基本と、ECMAScript(JavaScript)における利用方法を紹介する連載。初回は、ECMAScriptのバージョンと正規表現の機能対応、ブラウザでのサポート状況、正規表現を扱う方法(オブジェクト、メソッド)について。
正規表現とは
そもそも「正規表現」とは何でしょうか? それを理解するために、コマンドプロンプトや「Microsoft Word」などにおけるワイルドカードを見直します。
ワイルドカードとは何か?
「ワイルドカード」とは、もともとトランプなどのカードゲームにおける「万能カード」を意味しています。転じて、検索などのグロブパターンに使用される文字をワイルドカードといいます。Windowsのコマンドプロンプトでファイル名を指定するときのアスタリスク(*)とクエスチョン(?)が代表的なワイルドカードで、それぞれ「任意の文字列」「任意の1文字」を意味します。
「*.txt」「?.txt」のように指定すると、それぞれ「<任意の文字列>.txt」「<任意の1文字>.txt」に一致するファイル名を指定できます。ワイルドカードを使うことで、複数のファイル名に共通するパターンを指定することができ、ファイルの検索などが便利になるわけです。
Wordでは、検索のダイアログボックスで[オプション]を展開し、[あいまい検索(日)]をオフにして、[ワイルドカードを使用する]にチェックを入れることで、ワイルドカードによる検索が可能になります。同じダイアログボックスで[特殊文字]をクリックすると、ワイルドカードに相当する特殊な文字を検索ボックスに入力できます。上記の「*」「?」に相当する「任意の1文字」「0以上の文字」に加えて、文字の範囲や繰り返し回数が指定できるなど、グロブより細かな指定ができるようになっています。単純な文字列による検索にとどまらず、一定のパターンの文字列を検索することができ、文書作成の効率をアップさせます。
正規表現とワイルドカードの違い
正規表現とは、このワイルドカードをより複雑なパターンに適用できるように高度化したものです。上記のコマンドプロンプトやWordの検索機能を使っていて、「こんな指定ができたらいいのに!」と思ったことはないでしょうか? そのようなときに正規表現が活用できれば、さらに便利に効率良く作業ができるようになるに違いありません。
正規表現の歴史は古く、1950年代には最初のモデルが存在していました。そして、UNIX系のツール(「ed」など)に取り入れられ、一気に広まりました。edはテキストエディタの一つで、正規表現による検索や置換を可能にしています。さらに「grep」「vi」などのツールにも取り入れられ、UNIXにおけるパターン検索の地位を定着させます。ツールに加えて「AWK」「Perl」などのプログラミング言語も正規表現をサポートするようになり、今ではほとんどのプログラミング言語が正規表現をサポートします。
このように、正規表現はツールやプログラミング言語に次々と取り入れられてきましたが、実装がツールやプログラミング言語によって異なるという問題も発生しました。しかし現在では、「POSIX」(Portable Operating System Interface)というIEEE(Institute of Electrical and Electronics Engineers)が定めた規格で標準化されており、ほとんどの部分を共通に扱えるので、正規表現のポータビリティは非常に高いものになっています。
既述の通り、正規表現は決して新しい技術ではありません。しかし、業務の効率化と自動化による生産性の向上、ビジネスにおけるコンピュテーショナルシンキング(計算機的思考)の必要性が話題になる今、正規表現を学ぶことには意義があることでしょう。WordのワイルドカードはWordでしか使えませんが、標準化された正規表現なら、あらゆる場所で活用の機会があるわけです。一度基本を覚えてしまえば、UNIXやLinuxのツール、各種のエディタ、プログラミング言語など、場所を選ばず活用できます。
本連載「ECMAScriptで学ぶ正規表現」ではECMAScriptにおける正規表現を紹介します。正規表現はさまざまな言語で利用できますが、ECMAScriptは特別な準備なくWebブラウザだけで利用できます。「ECMAScript 2018」では正規表現のサポートも大幅に強化されました。後述のようにECMAScriptも標準化されており、クライアントサイドにとどまらずサーバサイドでも活躍の場を広げていることから、正規表現を学び実践する場としてはこれ以上のものはないと考えられます。
ECMAScriptのバージョンと正規表現
「ECMAScript」は、Ecma Internationalの下で標準化されているJavaScriptの規格です。「エクマスクリプト」といいます。Ecma(エクマ)とは「European Computer Manufacturers Association」の略で、日本語では欧州電子計算機工業会のことです。1961年に設立し、情報通信技術に関する規格を統一、標準化する機関であり、またその規格も意味します。ECMAScriptは、「ECMA-262」という規格で定められており、ISO/IEC JTC 1では「ISO/IEC 16262」として、日本でも「JIS X 3060:2000」として、規格を定めています。
ECMAScriptでは、バージョンを「エディション」(版)で管理します。初版の発行は1997年6月で、大きく変化したのはエディション6のときです。エディション5までは、エディション番号そのものを「ECMAScript Edition 5th」のように用いていましたが、Edition 6からは発行年を用いた「ECMAScript 2015」のように、毎年改訂されるようになりました。これをさらに略して「ES2015」といった表記がWeb開発の世界では一般的に用いられています。2021年12月の原稿執筆時点での最新版は「ECMAScript 2021(Edition 12)(ES2021)」です。
ECMAScriptでは、改訂によって正規表現にまつわる機能が追加、改良されています。それらをざっくりまとめると表1の通りです(ES2015以降)。これらについては、本連載を通じて紹介する予定です。
エディション | 内容 |
---|---|
2015 | Unicodeサポートの拡充。U+10000以上の文字が正しく扱えるようになった yフラグにより指定位置からのマッチングが可能になった well-knownシンボルによるカスタマイズが可能になった |
2018 | sフラグによる改行文字のマッチングを制御可能になった 名前付きキャプチャーグループが使用可能になった 先読みに加えて後読み機能もサポートされた Unicodeプロパティの利用が可能になった |
2022 | dフラグによるマッチング結果からのインデックス範囲の取り出しが可能になった |
表1 ECMAScriptにおける正規表現サポート |
ECMAScriptとJavaScript
上記で「ECMAScriptは、JavaScriptの標準規格」と書きました。とすると、ECMAScriptとJavaScriptという区別があるのはなぜなのでしょうか? JavaScriptは、ECMAScriptのWebブラウザにおける実装を指します。Webブラウザで使用できるECMAScriptがJavaScriptなのです。
JavaScriptの他にもECMAScriptの実装があります。例えば、「Internet Explorer」ではIE8まで「JScript」と呼んでいて、「Adobe Flash」では「ActionScript」と呼んでいました。つまり、ECMAScriptという標準があり、そしてWebブラウザなどのアプリケーションごとにJavaScriptやActionScriptといった実装があるのです。仕様と実装、と考えれば分かりやすいと思います。
Webブラウザに限ってしまえば、「そこで動作するJavaScriptは、どのECMAScriptのエディションをサポートするのか」が重要になります。ただ、代表的なモダンブラウザ(「Google Chrome」「Apple Safari」「Mozilla Firefox」「Microsoft Edge」)の最新版では、ECMAScript 2021のサポートを完了したので、あえて古いWebブラウザを利用しなければならない場合以外は、特に意識する必要はないでしょう。
Webブラウザで使う方法
ECMAScriptによる正規表現を実践するには、Webブラウザ上の実装であるJavaScriptを利用するのが最も簡便です。特に、本稿の後半で紹介するJavaScriptのメソッドのサンプルのようなものなら、Webブラウザの開発者ツール(ブラウザによって名称は異なる)を使ってすぐに試せます。Chrome(DevTools)の場合、空のタブを開いた状態で、ウィンドウ右上の3点メニューをクリックし、「その他のツール」→「デベロッパー ツール」と選択することで、DevToolsがアクティブになります。
ここでは、「DevTools is now available in Japanese!」と表示されています。これは、「日本語になります」というサジェスチョンなので、「日本語にしたい」という読者は「Switch DevTools to Japanese」ボタンをクリックしてください。「Always match Chrome's language」ボタンをクリックすると、Chromeの言語設定に合わせることができます。「Don't show again」ボタンをクリックすると、このサジェスチョンが出なくなるので、その後に言語を変更したい場合には設定ボタン(歯車のアイコン)から変更してください。
ここでは、Chromeの言語設定に合わせました。そして、上部のタブから「コンソール」をクリックして切り替えます。これで準備が整いました。
JavaScriptからWebブラウザの画面に何かを書き出すには「document.write()」メソッドなどを用いますが、コンソールで実行するときには「console.log()」メソッドを用います。以下の例では、コンソールに「Hello, World!!」というおなじみのメッセージを表示します。
console.log('Hello, World!!');
下図のように、コンソール上のプロンプトに入力し、「Enter」キーを押すと実行結果が表示されます。「undefined」と表示されるのは、プログラムの実行結果が有効な値として評価されなかったからなので、ここでは無視して構いません。
正規表現を専門に扱うRegExpクラス
ECMAScriptで正規表現を扱うには、専用の「RegExp」クラスとそのメソッド、「String」クラスに用意された一部のメソッドを使用します。正規表現主体のRegExpクラスと、文字列主体のStringクラスというように使い分けます。まずはRegExpクラスを紹介します。
RegExpクラスの使い方
RegExpクラスは、正規表現専用のクラスです。コンストラクタに、マッチングのパターンを指定してインスタンスを生成します。次の例は、パターンが「oo」であるRegExpオブジェクト「regex」を生成します。このようにRegExpクラスでは、正規表現の指定そのものがオブジェクトになります。
const regex = new RegExp('oo'); // コンストラクタ
正規表現には、「フラグ」という詳細情報を指定することもできます。次の例はフラグ('g')も指定したものです。フラグについては連載の後の回で取り上げる予定なので、ここではそのような生成方法もあるぐらいの認識で大丈夫です。
const regexg = new RegExp('oo', 'g'); // コンストラクタ(フラグを指定)
インスタンスの生成は、上記のコンストラクタ(関数)を用いる方法に加えて、「リテラル記法」を用いることもできます。リテラル記法では、パターンをスラッシュ(/)で囲み、フラグを指定する場合は2番目のスラッシュに続けます。このとき、クラス名と「new」演算子は省略するのが普通です。これだけで、RegExpクラスのインスタンスが生成されます。次の例は、上記の2つの例と同等です。
const regex = /oo/; // リテラル記法(フラグなし) const regexg = /oo/g; // リテラル記法(フラグあり)
コンストラクタとリテラル記法の違いは、パターンがコンパイルされるタイミングです。リテラル記法では、パターンは評価時にコンパイルされます。ループ中で毎回同じパターンを使う場合は、リテラル記法を用いた方が効率が良くなります。コンストラクタを使う方法では、パターンは作成されるたびにコンパイルされます。ループ中でパターンが変化する(変数から生成する、外部から入力されるなど)場合は、コンストラクタを使用するといった使い分けになります。
また、コンストラクタを介したリテラル記法を用いることもでき、この場合は次のように記述します。ただし、フラグの付け替えが必要なときなどにあえて使う以外は、上記のシンプルなリテラル記法を用いるのが一般的です。本連載では、特に意図がない限りはリテラル記法を用いて正規表現を指定します。
const regex = new RegExp(/oo/); // コンストラクタ+リテラル記法(フラグなし) const regexg = new RegExp(/oo/, 'g'); // コンストラクタ+リテラル記法(フラグあり)
RegExpクラスには、2つのメソッドが用意されています。生成したオブジェクトとメソッドを用いて、マッチングを調査します。
マッチングするか調べるだけのtest()メソッド
「test()」メソッドは、指定する文字列内にパターンに一致するものがあるかどうかを調べ、結果を論理値(trueまたはfalse)で返します。どのように一致したのかは分かりません。test()メソッドを使うメリットは、処理が軽いことです。以下の例は、4桁の数字が文字列中にあるかどうかを調べて結果を表示します。
const regex = /[\d]{4}/; // 4桁の数字を表す正規表現パターン const str = '225-0002'; // 「225-0002」が検索対象 console.log(regex.test(str)); // 「true」
[NOTE]正規表現パターンについて
ここで使用しているパターンの詳細は連載第2回以降で述べるので、ここでは「4桁の数字を意味する」という認識だけで大丈夫です(以降も同様です)。なお、パターンにある\やカッコなどの記号は正規表現において特殊な意味を持つので、文字としてこれらの記号を使いたい場合には「エスケープ」という記法が必要になります。詳細は連載第2回で改めますが、パターンを書き換えて試したい場合は注意してください。
なお、本連載のサンプルはGitHubで公開しています。下記URLからアクセスしてください。今回のサンプルは、summaryフォルダに配置されます。
- https://github.com/wateryinhare62/atmarkit_es_regexp/
マッチングした文字列を取り出すexec()メソッド
exec()メソッドは、文字列内にパターンに一致するものがあるかどうかを調べ、結果を配列で返します。一致するものがない場合は、nullを返します。配列の最初の要素は、一致した部分の文字列です。また、gフラグなどが指定された場合には、最後にマッチングした位置から検索が再開されます(gフラグは、複数箇所のマッチングを調べるフラグ)。次の例は、パターンに一致した文字列が見つかった場合にのみ、その内容を出力します。使っている正規表現のパターンはtest()メソッドと同じです。
const regex = /[\d]{4}/; // 4桁の数字を表す正規表現パターン const str = '225-0002'; // 「225-0002」が検索対象 const array = regex.exec(str); if (array !== null) { console.log(`${array[0]} が見つかりました。`); // 「0002 が見つかりました。」 }
文字列全般を取り扱う汎用的なStringクラス
Stringクラスは、文字列を取り扱う汎用(はんよう)的なクラスです。メソッドの呼び出し時に、正規表現パターンをRegExpオブジェクトで指定します。Stringクラスには、正規表現を扱うメソッドが6つ用意されています。
マッチングした文字列を配列で取得するmatch()メソッド
match()メソッドは、パターンに一致した文字列を配列で返します。一致するものがない場合はnullを返します。RegExpクラスのexec()メソッドから手軽に一致文字列を取得できます。以下の例は、3桁または4桁の数字が文字列中にあるかどうかを調べて、全ての結果を表示します。
const regex = /[\d]{3,4}/g; // 3桁または4桁の数字を表す正規表現パターン const str = '225-0002'; // 「225-0002」が検索対象 const array = str.match(regex); if (array !== null) { console.log(array); // 「['225', '0002']」 }
マッチングした文字列を反復子で取得するmatchAll()メソッド
matchAll()メソッドは、パターンに一致した文字列を返します。match()メソッドにgフラグを指定した場合と同じですが、反復子(イテレータ)を返す点が異なります。matchAll()メソッドを使う場合、RegExpオブジェクトにgフラグを指定する必要があります(指定しないとエラーになります)。次の例は、match()メソッドと同様ですが、結果の配列からfor文を用いて値を取り出して表示しています。
const regex = /[\d]{3,4}/g; // 3桁または4桁の数字を表す正規表現パターン const str = '225-0002'; // 「225-0002」が検索対象 const array = str.matchAll(regex); for (const match of array) { console.log(match[0]); // 「225」「0002」 }
マッチした位置を調べるsearch()メソッド
search()メソッドは、文字列中のパターンに一致した文字列の位置を0からの値で返します。一致しない場合には-1を返します。本来は文字列の単純な検索用のメソッドですが、引数にRegExpオブジェクトが与えられた場合は、正規表現パターンによるマッチングを行います。RegExpクラスのtest()メソッドと同様にマッチングの有無を調べるのに使えますが、位置を取得できるので後処理に役立つ点が異なります。
次の例は、4桁の場合と5桁の場合とで数字が文字列中にあるかどうかを調べて、その位置を表示します。
const str = '225-0002'; // 「225-0002」が検索対象 const regex1 = /[\d]{4}/; // 4桁の数字を表す正規表現パターン console.log(str.search(regex1)); // 「4」 const regex2 = /[\d]{5}/; // 5桁の数字を表す正規表現パターン console.log(str.search(regex2)); // 「-1」
マッチした文字列を置換するreplace()メソッド
replace()メソッドは、文字列中からパターンを検索し、一致したものを別の文字列で置き換え、置き換えた後の文字列を返します。本来は文字列の単純な置き換え用のメソッドですが、引数にRegExpオブジェクトが与えられた場合、パターンによるマッチングをし、gフラグが指定された場合には全ての一致する文字列が置換されます。次の例は、4桁の数字が文字列中にあるかどうかを調べて、あれば「9999」に置換してその結果を表示します。
const regex = /[\d]{4}/; // 4桁の数字を表す正規表現パターン const str = '225-0002'; // 「225-0002」が検索対象 console.log(str.replace(regex, '9999')); // 「225-9999」
マッチした文字列を全て置換するreplaceAll()メソッド
replaceAll()メソッドは、文字列中からパターンを検索し、一致したものを別の文字列で全て置き換えます。replace()メソッドとの違いは、検索対象が正規表現であるかどうかにかかわらず、一致した文字列全てを置換することです。replaceAll()メソッドを使う場合、matchAll()メソッドと同様、RegExpオブジェクトにgフラグを指定する必要があります(指定しないとエラーになります)。次の例は、2文字の「o」(オー)を検索し、一致する箇所を全て「ee」に置き換えてその結果を表示します。
const regex = /o{2}/g; // 2文字のoを表す正規表現パターン const str = 'Boo Foo Woo'; // 「Boo Foo Woo」が検索対象 console.log(str.replaceAll(regex, 'ee')); // 「Bee Fee Wee」
[NOTE]replaceAll()メソッド
replaceAll()メソッドは、ES2021でサポートされた新しいメソッドです。
マッチした文字列で分割するsplit()メソッド
split()メソッドは、指定したパターンを“区切り”として文字列を分割し、結果を文字列の配列で返します。本来は文字列の単純な分割用のメソッドですが、引数にRegExpオブジェクトを与えた場合、パターンによるマッチングをします。次の例は、空白文字で分割し、結果を表示します。
const regex = /\s/; // 空白を表す正規表現パターン const str = 'Boo Foo Woo'; // 「Boo Foo Woo」が検索対象 console.log(str.split(regex)); // 「['Boo', 'Foo', 'Woo']」
まとめ
連載第1回の本稿では、正規表現のあらまし、ECMAScriptの概要、JavaScriptにおける実践方法、そしてECMAScriptが定める正規表現の機能、RegExpクラスとStringクラスを紹介しました。まだ何も複雑なことはできていませんが、正規表現を試す上での下地ができたと思います。次回は、実用への第一歩として基本的なパターンの書き方を紹介します。
筆者紹介
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.
関連記事
- C#で正規表現を利用するためのメソッド
C#には正規表現を利用するためのRegexクラスが標準で用意されている。Regexクラスが提供するメソッドで正規表現を使用するための基本をまずは見てみよう。 - 【WSL入門】第4回 bashの展開機能と正規表現の基礎
WSLのメリットには、さまざまなコマンドで利用できる「正規表現」とシェルのbashの展開機能がある。この2つを理解することで、WSLの利用価値が大きく向上する。WSL入門の最終回として、この2つの機能を紹介しよう。 - JavaScript関連プロジェクトのランキング「2020 JavaScript Rising Stars」が公開
「Best of JavaScript」プロジェクトは、2020年に最も注目を集めたJavaScriptプロジェクトなどのランキング「2020 JavaScript Rising Stars」を発表した。総合ランキングでは、過去5年間首位を維持した「Vue.js」を抜いて、「Deno」が首位を獲得した。