(Excel)REGEX~関数で使える正規表現

正規表現について

 正規表現は通常の文字(リテラル)と特殊な記号(メタキャラクタ)を組み合わせて、文字列のパターンを表現するのに用いられます。簡単な例だと「F..D」という正規表現は「『F』で始まり『D』で終わる4文字」を表します。つまり「FIND」や「FOOD」などがこの正規表現にマッチ(該当)します。後述するように原則として部分一致で判定されるので、「文字列中の『FIND』や『FOOD』にマッチする」といった方が正確かもしれません。
 COUNTIF関数などで利用できるワイルドカードと似ていますが、ワイルドカードのメタキャラクタが3種(*?とエスケープ用の~)しかないのに比べ、正規表現では様々なメタキャラクタが用意されており、複雑なパターンを表現できます。
 そしてExcelではREGEXTEST,REGEXEXTRACT,REGEXREPLACEの各関数で正規表現を利用した文字列の判定・抽出・置換ができます。本記事ではこれらの関数の使用例を交えながら、使用できる正規表現の主な内容を紹介します。
 なお、XLOOKUPとXMATCHの各関数でも検索値(第1引数)に正規表現を利用できます(ただし、一致モードの引数を指定する必要があります)。このほかPython in Excelでも正規表現モジュールreを利用することで正規表現の使用が可能となっています。

簡単な使用例

 次の画像では、C列にREGEXTEST関数を使った数式を入力してB列の文字列が「山」の字を含んでいるか判定しています。
 含んでいればTRUE、いなければFALSEとなります。

 C3セル(下方にフィルコピー)

=REGEXTEST(B3,"山")

 第1引数で対象文字列を、第2引数で正規表現を指定することで、「正規表現で表されるパターンを含むかどうか(部分一致)」が判定されます。メタキャラクタ(特殊な文字)を使っていないのでどこが正規表現なのかと思われるかもしれませんが、この部分一致の判定自体が正規表現の特徴といえます。



 次の画像ではREGEXEXTRACT関数を使い、文字列中の数字だけを一括抽出しています。
 この例だけでも従来の文字列関数より強力な機能があることが分かると思います。

 D3セル

=REGEXEXTRACT(B3,"[0-9]+",1)

 ここでは第2引数(正規表現)にメタキャラクタ(特殊な文字)を使っています。
 「[0-9]+」は「1文字以上の並んでいる半角数字」を意味しています。該当する部分が3つありますが、第3引数を「1」とすることですべてを取得することができます(省略すると最初の「800」だけ抽出されます)。
 また、「[0-9]+」を「[0-9]」にすると「1文字の半角数字」になるので、1文字ずつの8つの数字が抽出されます。

ワイルドカードによる判定との違い

 上記のREGEXTEST関数の例でわかるように、正規表現が対象文字列に該当するかどうかの判定は部分一致判定で行われます。さらに言うと対象文字列中の複数の部分にマッチしてそれらを区別することができるので、REGEXEXTRACT関数の例のように複数の部分文字列を取得することもできます。
 一方でCOUNTIFやVLOOKUPなどでのワイルドカードを使った判定は完全一致判定といえます。たとえば「*山*」というパターンを使うことで「『山』を含む(部分一致)かどうか」を判定できますが、これは見方を変えると「*山*」というパターンに完全一致しているかどうかの判定にほかなりません。
 逆に正規表現でも「.*山.*」とすれば(①0文字以上の文字②山③0文字以上の文字、の順に並んでいるという意味)ワイルドカードの「*山*」と同様のニュアンスになりますが、そこまで作り込まなくても部分一致判定されます。
 一見なんてことはないですが、判定や抽出の条件指定を考える際に忘れやすいポイントです。

メタキャラクタ

 正規表現のメタキャラクタは非常に種類が多いです。しかしよく使うものは限られていますし、限られたものでもかなり高度なパターンの設定が可能です。

¥の表示について

 正規表現では「¥」の記号が多用されますがExcelの数式バーではバックスラッシュとして表示されます。
 以下でも文章上は「¥」を用いますが、数式サンプル(四角い欄内の数式)内ではバックスラッシュで表示しますのでご注意ください。

文字クラス(文字の種類の指定)

 ある種の文字を指定するメタキャラクタです。
 自ら文字の種類を定義できるものと、定義済みのものがあります。

記号意味
.任意の1文字
ワイルドカードの「?」に相当しますが、改行にはマッチしません
[abc]列挙した文字のうちいずれか1文字
(例はa,b,cのいずれか1文字という意味)
[a-z]指定した範囲のうちいずれか1文字
数字やひらがな、カタカナ等も同様に範囲指定ができます
(例はaからzまでの文字(英半角小文字)という意味)
abc|def指定した文字列(2文字以上可)のうちいずれか
(例はabcとdefのいずれかという意味)
[^abc]列挙した文字以外の1文字
(例はa,b,c以外の文字という意味)
[^a-z]指定した範囲以外の1文字
(例はaからzまでの文字(英半角小文字)以外の文字という意味)
[[a-z]&&[^ceg]]2つの文字の集まりのうち共通するもの1文字
(例はaからzまでの文字(英半角小文字)のうちc,e,g以外の1文字という意味)
\w半角英字(a~zとA~Z),半角数字(0~9),アンダースコア(_)のいずれか1文字
\W半角英字(a~zとA~Z),半角数字(0~9),アンダースコア(_)以外の1文字
\d半角数字(0~9)のいずれか1文字
(「[0-9]」に相当)
\D半角数字(0~9)以外の1文字
\n改行
\エスケープ
(「\.」は上記の定義による「任意の1文字」ではなく「.」の文字という意味)

 これらは通常の文字と、あるいは互いに組み合わせて使用することができます。単純な例として「[abc]de」は「a,b,cのいずれかの文字の次にdeが続くもの」つまり「ade」「bde」「cde」にマッチしますし、「[ceg].t」は「cat」「cut」「eat」「get」などにマッチします。
 以下は補足です。

  • 文字の列挙はどのような順番でもよいので「[gaqcz]」などとしてもよいですが範囲指定の場合は順序を守る必要があり、「[z-a]」のようにするとエラーになります。
  • 「[^a]」は「a以外(ひらがなや漢字なども可)の1文字」という意味であって「aを含まない」という意味ではありません。
  • 「cats」と「dogs」にマッチする正規表現は「cats|dogs」のほか「(cat|dog)s」とすることもできます。なおカッコには別の効果もあります(下記のグループ化とキャプチャ参照)。
  • 「[[a-z]&&[^ceg]]」の例に現れる「&&」は2つの集合の共通部分、つまりANDに相当します。「[[abc]&&[xyz]]」などとすると該当する文字が1つもないので何の文字にもマッチしません。なお「[[a-z]-[ceg]]」のような演算は無効です。
  • 「[abc]」のような列挙の内部では「.」のほか「$」といったメタキャラクタ(下記参照)が特別な意味を持たずリテラル(そのままの文字)として扱われます。先頭に「-」を置いたり「^」を2文字目以降に置いた場合も同様です。ただし途中に「-」を置いた場合は範囲指定の意味になるなど、順番を変えただけで意味が全然変わってしまうので、適宜エスケープ(¥)しておいたほうが無難です。

量指定子(繰り返し回数の指定)

 通常の文字や上記の文字クラスと共に使用することで、同じ文字や文字クラスの繰り返しを省略できます。
 2つのグループに分けて紹介します。


 まず1つ目のグループです。

記号意味
*0回以上の繰り返し(長くマッチ)
+1回以上の繰り返し(長くマッチ)
?0回か1回の繰り返し(0回より1回にマッチ)
{n}n回の繰り返し
{n,}n回以上の繰り返し(長くマッチ)
{n,m}n回以上m回以下の繰り返し(長くマッチ)

 簡単な例として「..........」は任意の10文字(それぞれの文字は異なっていてよい)にマッチしますが、これを「.{10}」とすることができます。
 「*」や「?」における「0回」という部分が気になるかもしれません。例えば「a*」とか「a?」といった正規表現はどのような文字列に対してもそのどこかにマッチします(aを含まない文字列ではアンカー(下記参照)にマッチし、空白セルにもマッチします)。「a」の字があってもなくてもいいので当然ですが、これらは他の文字と組み合わせることに意味があり、例えば「colou?r」は「color」と「colour」にマッチします。
 なお、ワイルドカードとの比較では「.」「.*」がそれぞれワイルドカードの「?」「*」に相当します。
 上記の量指定子は、該当する文字が続く限り長くマッチしようとするので最長マッチなどと呼ばれます。ただし「最長」という言葉には注意が必要です。一例として上記のREGEXEXTRACT関数の例の中の「[0-9]+」を「文字列中の最も長い数字」と解釈してしまうと抽出されるのは「10300」だけになるはずですが実際は3つの数字が抽出されています。より正確には「1つ1つのマッチにおいてなるべく長くマッチしようとする」ものといえます。


 ちょっとした注意点です。次の画像では「*」を使い、全角カッコを含めてその間を抽出しようとしています。最初のカッコの間「(いわし)」を抽出することを意図していますが失敗しています。

 C3セル(失敗)

=REGEXEXTRACT(B3,"(.*)")

 B3セルの文字列にはカッコの組が2つ含まれるため、正規表現のうち「.*」の部分が2つ目の閉じカッコの手前までマッチしてしまっています。「.」はカッコにもマッチし、「*」を加えることで少しでも長くマッチしようとするのでこうなりますが、このように記したときに「.」がカッコにマッチしないように勘違いしてしまいがちです(ただし、直接指定した「)」の方を最後の閉じカッコにマッチさせる調整がはたらくため、実際に「.*」の部分は2つ目の閉じカッコにマッチしません)。
 解決する例については以下の「.*?」を使う例をご覧ください。


 続いて量指定子の2つ目のグループです。

記号意味
*?0回以上の繰り返し(短くマッチ)
+?1回以上の繰り返し(短くマッチ)
??0回か1回の繰り返し(1回より0回にマッチ)
{n}?n回の繰り返し
({n}と同じ)
{n,}?n回以上の繰り返し(短くマッチ)
{n,m}?n回以上m回以下の繰り返し(短くマッチ)

 1つ目のグループの量指定子に「?」を加えたものです。これにより、これにより該当する文字が続くとしても短くマッチしようとするので最短マッチなどと呼ばれます。
 実用的にさほど重要でないものもありますが「*?」は比較的多用されます。


 次の画像が実例です。1つ上の画像の例では「*」を使っていましたが、こちらでは「*?」を使うことで最初のカッコとその間の部分だけを抽出しています。

 C3セル

=REGEXEXTRACT(B3,"(.*?)")

 正規表現中の「.*?」が1つ目の閉じカッコの手前まで、「)」が閉じカッコそのものにマッチします。これより短くマッチさせようとしても閉じカッコが含まれなくなるのでそうはならず、結果的に1つ目の閉じカッコまでが抽出されます。



 ただしこの「.*?」にも注意が必要です。
 次の画像は同じ数式を別の文字列(二重の全角カッコを含む)に適用した例です。短くマッチするのだから内側のカッコとその間を抽出する…ようにも思えますが結果はそうなりません。

 正規表現中の「(」が1番目の開きカッコにマッチし、あとは上記例と同様に1番目の閉じカッコまでマッチするので、意図しない抽出結果となります。
 「長くマッチする」とか「短くマッチする」というのはマッチを(末尾側の)どこで打ち切るかの問題であって、どこから始めるかには影響しない点に注意する必要があります。


 なお画像は省略しますが、最も内側のカッコを抽出する数式の一例は次のようになります。

=REGEXEXTRACT(B3,"([^()]*?)")

 ちょっと見づらいですが[ ]の文字クラスを使い、①開きカッコ②開きカッコでも閉じカッコでもない文字列(短く)③閉じカッコ、が並んでいる部分を抽出しています。

アンカー(ゼロ幅マッチ)

 文字のない部分、つまり文字列の先頭や末尾、文字の間にマッチさせるためのメタキャラクタで、アンカーやゼロ幅マッチなどと呼ばれます。
 正確には先読みと後読みもこれに属しますが、これは以降の節で紹介します。

記号意味
^文字列の先頭
$文字列の末尾
\b上記の「\w」(a~z,A~Z0~9,及び「_」)が1文字以上ある部分の前後の位置
\B上記の「\w」(a~z,A~Z0~9,及び「_」以外)が1文字以上ある部分の前後の位置

 以上のほか、空文字列("")が先頭・末尾及び文字間の位置にマッチします。これらの位置に長さ0の文字列があると解釈することもできます。


 次の画像では、文字列の先頭と、文字列の先頭の1文字をそれぞれ「★」に置き換えています。

 C3セル(先頭を置き換える)
 C7セル(先頭の1文字を置き換える)

=REGEXREPLACE(B3,"^","★")
=REGEXREPLACE(B7,"^.","★")


 上の数式(C3セル)では第2引数(置換元文字列)を「"^"」とし、文字列の先頭の文字のない部分を置き換えています。「^」は文字列の先頭を表しますが、「先頭の~」といった修飾的な意味ではなく単独で先頭そのものの位置を指していることがわかります。ただし置き換え後の文字列から「^」がなくなることはなく、先頭にはやはり「^」が残ります。
 下の数式(C7セル)では第2引数を「"^."」としています。「文字列の先頭とそれに続く1文字」が「★」に置き換えられる、つまり先頭の1文字が置き換えられるという結果になります。



 次はもっと端的な例です。
 REGEXREPLACE関数の第2引数を空文字列(長さ0の文字列)にすることで、文字列の先頭・末尾・文字の間にそれぞれ「★」を挿んでいます。

 C3セル

=REGEXREPLACE(B3,"","★")

 SUBSTITUTE関数だと何も変わらない結果となりますが、こちらでは異なる結果となります。
 なお、第2引数そのものを省略(カンマの連続にする)しても同じ結果となります。

グループ化とキャプチャ

 ある正規表現を1つのまとまり(グループ)として扱うことができます。単純な例では2文字以上の文字列を量指定子の対象とすることができます。
 また、マッチした文字列部分を変数のように記録(キャプチャ)して参照(再利用)することができます。これも従来の文字列関数では利用できない機能です。

記号意味
(abc)(キャプチャグループ)
カッコ内の正規表現を1つのグループとして量指定子による繰り返しの対象にできます
マッチした文字列にはカッコごとに\1,\2...といった番号が付与され、マッチした文字列を参照(使用)することができます
(例はabcという文字列を1つのまとまりとして\1という番号を付与したもの)
(?:abc)(非キャプチャグループ)
カッコ内の正規表現を1つのまとまりとして量指定子による繰り返しの対象にできます
番号は付与されません
(?<名前>abc)(名前付きグループ)
キャプチャグループと同様ですが、番号のほかに独自の名前(ただし「\g<名前>」のように記す)で文字列を参照することができます

 例として「(very){1,3}good」は「verygood」「veryverygood」「veryveryverygood」にマッチします。


 以下では番号による参照の例を見てみます。
 まずはREGEXEXTRACTによる抽出の例で、文字列中の「同じ文字列の繰り返し部分」を抽出しています。

 C3セル(下方にフィルコピー)

=REGEXEXTRACT(B3,"(.+)\1+")

 「.+」つまり1文字以上の文字列をカッコで囲み「\1」という番号を与えています。そしてそれをその直後で参照しています。これにより、ある1文字以上の文字列が繰り返される部分が抽出されます。
 短いわりに難しいことをやっていますが、このような例から正規表現がどのように文字列を探索してマッチ部分を割り出しているのか考えてみるのも面白いかもしれません。
 なお、最後の「+」がないと2回目の繰り返し部分までしか抽出しません。



 次はREGEXREPLACE関数を使った置換の例です。
 数字の後に「円」という単位を付け加えています。

 C3セル

=REGEXREPLACE(B3,"([0-9]+)","\1円")

 ここでも「[0-9]+」つまり1文字以上の半角数字をカッコで囲み「\1」という番号を与えています。そして第3引数(置換文字列)で参照し、直後に「円」を加えています。これですべての数字の後に「円」が付きます。
 ところで第3引数を「JIS("¥1")」とか「ASC("¥1")」にすれば特定の文字だけ全角⇔半角変換できるように思えますが、単に「¥1」や「¥1」に置き変わるだけでうまくいきません。ただしREPT関数などはなぜかうまくいきます。



 次は名前付きグループとキャプチャの例です。
 内容は1つ上の例と同じです。

 C3セル

=REGEXREPLACE(B3,"(?<数字>[0-9]+)","\g<数字>円")

 「[0-9]+」にマッチする文字列に対し「数字」という名前を付けて、第3引数で参照しています。
 数式内でグループの追加や削除があっても番号がズレないのがメリットですが、見た目も複雑なのであまり使う機会はなさそうです。
 なお、名前を付けたうえで番号で(¥1)参照することもできます。

先読みと後読み

 アンカー(文字のない部分。上記参照)のうち、条件に合致するものを抽出したものです。
 条件に合致するアンカーがマッチするので、他の文字を伴わなくてもアンカーを処理(置換等)の対象にできますが、たいていは他の文字と共に使用されます。

記号意味
(?=abc)(肯定先読み)
末尾側(右側)の隣が~がであるアンカー(文字のない部分)
(例は右側の隣がabcであるアンカーという意味)
(?!abc)(否定先読み)
末尾側(右側)の隣が~がでないアンカー
(例は右側の隣がabcでないアンカーという意味)
(?<=abc)(肯定後読み)
先頭側(左側)の隣が~であるアンカー
(例は左側の隣がabcであるアンカーという意味)
(?<!abc)(否定後読み)
先頭側(左側)の隣が~でないアンカー
(例は左側の隣がabcでないアンカーという意味)


 以下では肯定先読みの例を示します。
 次の画像では文字列中の数字のうち、後ろに「円」がついた数字だけを抽出しています。

 C3セル

=REGEXEXTRACT(B3,"[0-9]+(?=円)")

 「[0-9]+」つまり数字の並びと、右側に「円」が続くアンカー、の2つが連なっている部分を抽出しています。
 結果的に「円」の字は抽出されず、数字だけが抽出されます。このように条件付けの対象を処理(抽出)対象から切り離すことができるのがポイントです。
 ただしこの例では「=REGEXEXTRACT(B3,"([0-9]+)円",2)」とする例も考えられます。



 また、先読みのメリットとして「処理対象の位置を進めずに済む」ことが挙げられます。いったん先を読みはするが戻ってきて処理を再開するという意味です。
 次の画像は先読みを使わなかった失敗例です。「各文字の間に★を挿む」ことを意図した関数の実行例ですが、2か所しか★が入らずうまくいきません。

 C3セル(失敗)

=REGEXREPLACE(B3,"(.)(.)","\1★\2")

 「2つずつの文字の間に★を挿む。これを全体に適用すれば各文字列の間に★が入るだろう」というのが数式の意図です。
 実際はまず先頭の2文字を処理対象としてその間に★を挿むところまではいいものの、次の処理対象は3文字目と4文字目となりその間に★が入る、そして5文字目は最後に1文字だけ残るため処理の対象にもならない、というように進むため意図通りの結果にはなりません。



 そこで次の画像では肯定先読みを使って対応しています。

 C3セル

=REGEXREPLACE(B3,"(.)(?=.)","\1★")

 1つ上の例では2文字ずつ処理が進んでしまうことが失敗の原因でした。そこで、先読みを使うことで1文字ずつ処理が進むようにしています。最初に1文字目を処理対象とし、次の2文字目を読んでその存在を確認したうえで1文字目の次に★を加えます。次の処理対象は2文字目となり、その次の3文字目を読んでその存在を確認したうえで2文字目の後に★を加えます。これを繰り返すことで最後の文字以外のすべての文字の次に★が加わるので意図する結果が得られます。
 なお、次のようにアンカーのみを置換対象としても同じ結果になります。

=REGEXREPLACE(B3,"(?<=.)(?=.)","★")

 前後に文字があるアンカーだけを★に置き換えています。

フラグ

 一言でいえば正規表現内に組み込まれた設定オプションです。
 ここで示すものはマッチモードとかモード修飾子といった呼び名の方が正確かもしれませんが、フラグと呼ぶこととします。

記号意味
(?i)大文字と小文字を区別せずマッチするようになります
(?m)改行が含まれる場合に各行の先頭が「^」に、末尾が「$」にマッチするようになります
(?s)「.」が改行にもマッチするようになります

 「(?im)」のようにして複数種を同時指定することもできます。


 次の画像はiフラグの例です。正規表現内の文字列はAPPLEですがiフラグを使用しているので文字列中のAppleにマッチし、判定はTRUEとなっています。

 C3セル

=REGEXTEST(B3,"(?i)APPLE")

 Excelの一致判定(=)は基本的に大文字と小文字を区別しませんが正規表現では通常区別します。しかしiフラグでこれを変更することができます。
 ただしREGEX~関数では引数で区別の有無を設定できるのでそちらを使った方がよいでしょう(XLOOKUP/XMATCH関数ではそのような引数がないので比較的重要です)。なお引数よりフラグの設定の方が優先されます。



 次はmフラグの例です。
 改行を含む文字列から、「島」で始まる行を抽出しています。

 C3セル

=REGEXEXTRACT(B3,"(?m)^島.*")

 フラグの効果で各行の先頭に「^」がマッチするようになっています。これを使って「島」で始まる行全体を抽出しています。
 なお、「.」は改行にマッチしないため「.*」が次の行まで抽出することはありません。そこで行の末尾の「$」は省略しています。
 同様の理屈で「含む」という条件なら「.*島.*」で済みます。



 次はsフラグの例です。文字列中の「b」以降を改行を含めてすべて抽出しています。
 (C3セルの書式設定で「折り返して全体を表示する」のチェックを入れています)

 C3セル

=REGEXEXTRACT(B3,"(?s)b.*")

 上記例でも述べたように「.」は改行にマッチしないのですが、フラグを使うことで改行にもマッチさせています。
 結果的に「.*」が改行を含めて文字列の末尾までを抽出します。

備考

その他の機能

 込み入った内容になるので省略しますが、主にバックトラック削減に用いられる絶対最大量指定子(「++」など)やアトミックグループ「(?>パターン)」が使えます。
 条件分岐や置換演算は使えない模様です。

正規表現のテストについて

 正規表現は難易度が高くREGEX~関数を操作するだけではなかなか理解が進みませんが、Web上にはJavaScript等を利用した正規表現チェッカーがあり、正規表現とマッチ部分の対応をリアルタイムで視覚的に確認できるので便利です。ただし正規表現の実装が異なる場合もあるのでその点はご注意ください。