正規表現(Regular Expression)について
はじめに
いまや正規表現というのは自動化だけではなく、様々なアプリケーションの文書上での検索/置換機能で利用できる事も多い基本的な機能となりました。もちろん自動処理においても特定の文字列を抽出・置換等に利用可能で、プログラミング上でも上手く使えば処理を簡潔に書けるようになる事も多くあります。
Adobe Creative Cloudアプリケーションでは、Indesignにおける検索置換機能や段落スタイルにおける正規表現スタイル等の様に今や必須の機能となっています。また、CCアプリケーションだけではなく各スクリプト言語においてもサポートされており、自動処理においてはテキスト加工作業においては必須の技術となりますのでしっかりと押さえておきたいものです。
と、まあ、ここまでの文章を読んで頂いた方の中には「あれっ?」て思った人もいらっしゃるかと思います。この記事は過去に自動化総合フォーラムに寄稿したものの再録です。2019年9月のAddobe Communityへの移行時にマージされずに破棄されたものです。正規表現の基本を簡潔にまとめたものですから消えたままにしておくのはもったいないのでこちらで公開することにしました。
メタキャラクタについて
まずは以下をご覧ください。
a
こちらの例は単純に「a」という文字列にマッチします。これはメタキャラではありません。この様にメタキャラクタ以外の文字は、通常の検索置換のように「その文字自身」にマッチします。ではメタキャラってなんだろうとなりますが、要するに何か特別な意味合いを持ったキャラクタ達の事なのです。
例を挙げてみましょう。
.
非常に判りにくいでが、ドット(ピリオド)です。これは任意の1文字にマッチする正規表現となります。
*
次に「*」は、直前の文字の0文字以上の連続にマッチします。これだけでは使えないメタキャラですから通常は次の様に組み合わせで利用します。
a*
このケースでは「a」にもマッチしますが、「aaaaaaaaaaaaaaaaa」という文字列にもマッチします。ここで注意しないといけないのが、0文字以上というところです。InDesignで正規表現検索を実行するとわかりますが、文字と文字の間も検索にかかります。[0-9]*という正規表現をInDesignで試してみてください。とてもヘンな挙動を見ることが出来ると思います。これは「空文字列」というものが引っかかるのでこういう挙動になります。
こういった場合は
aa*
というようににaに続く0文字以上のaの連続という風な一見特異な記述にします。しかし、多くの正規表現エンジンでは気のきいたメタキャラがあります。
a+
「+」というメタキャラは直前の文字1文字以上の連続にマッチします。
では、ここで非常によく使われるメタキャラの組み合わせを見てみます。
.*
頭から順にこの正規表現を評価してみますと、任意の文字の0個以上の連続ってなります。何でも引っ掛けてしまう正規表現で、もちろん空文字もひっかかります。ただし、こう書くと普通に文字列が引っかかりますので空文字列は気になりません。
.+
しかし0個以上の連続というのも中々馴染み難い表現ですからこの様に直前の文字列の連続というこちらの記述のほうがスマートに感じるかも知れません。
これら「*」や「+」の注意点ですが単体ではマッチしません。直前の1文字は必須ですので注意が必要です。
範囲を限定限定する
ここまで見てきた「何にでもマッチする」という表現を使うのは通常の場合限定的です。実際は数字だけとかアルファベット大文字だけ等を検索する場合が多い訳です。こういったケースでは[ ](ブラケット)を利用します。この[ ]の間に検索したいキャラクターを列記します。
[0123456789]
上の例ではブラケットで囲まれた部分に数字が並べられています。こうすると0か1か2か…8か9にマッチします。任意の1文字からブラケット内の1文字と選択範囲がぐんと狭くなりました。
しかしながら無駄に長いです。この様な記述方法では、大文字アルファベットだけでも26文字書かないといけません。小文字も含めると52文字、更に全角欧文を……気が遠くなりそうなのでこれぐらいにしますがこの記述は省略することが出来ます。
[A-Z]
この様に、大文字のアルファベットの場合はこの様にAZの間に「-」を入れて範囲を指定します。
[ぁ-ん]+
こちらはひらがなの例です。
[0-9]+
これは連続した数字を抽出してくれます。
否定
ここまでの説明でブラケットに囲まれた部分に色々と書けば検索対象に出来るという事を説明してきました。この範囲指定ですが、もう一手間加えると更に便利になります。
[^0-9]
違いはというと、頭のブラケットに続く「^」です。この位置に^がくると、続くキャラクタ群の否定となります。どういうことかというと「0-9」以外、回りくどいですが数字以外という事になります。
注意としては^は必ず先頭に来ます。逆に先頭に来ない「^」は検索対象キャラクターの一つとして処理されます。
[0-9^]
上記の例では「0-9」及び「^」がマッチします。
さて、ブラケット内では幾つかのお約束があります。
- メタキャラは基本的に効力を失い。該当するキャラクタが引っかかる。
- 「-」(ハイフン)は範囲を表す。「-」を検索対象に加えたい場合は先頭に記述。
- 「^」が先頭に来た場合は続くキャラクタ以外が検索対象となる。
- ブラケット「[]」を含めるたい場合、始まりの「[」はどこでも置けるが、閉じ側の「]」は先頭でないといけない。
ブラケット内は基本的に打ち込んだキャラクターがそのままが評価されます。例外は範囲を指定のための「-」や、後続を否定する「^」で、これらのキャラクタは本来あるべき位置(「-」ならキャラクタの間、「^」なら先頭)を外れた位置に入力されると検索対象のキャラクタとして認識されます。ブラケット([ ])ですが始まりのほうはどこでも認識しますが、閉じ側は先頭にあると検索対象として認識します。後ろに存在する場合は問答無用でブラケットを閉じる事になります。
[a-c]def]
この例だと、aまたはbまたはcで始まりdef]と続く文字列にマッチすることになります。
さて、先ほどのブラケット内でのお約束リストには入れてなかったお約束があります。ここでそれを見てみましょう。
- バックスラッシュでエスケープするメタキャラクタはメタキャラクタの性質を失わない。
というのがあります。バックスラッシュとアルファベット1文字で構成されるメタキャラクタと言うものが存在します。まずはいくつか例示してみます。
\n、\t、\r……
これらはテキストエディタの検索置換機能を利用する際にも良く見られるものです。順番にラインフィード、タブ、キャリッジリターンと呼ばれる制御コード系のキャラクタの表現です。
これらはブラケット内に記述した場合でも「\」と「n」の様に分解して認識する事はありません。あくまでも改行コードとして認識します。もし文字列として認識させたい場合は次の例の様に頭のバックスラッシュをエスケープします。
[\\n]
これで「\」または「n」と解釈されます。
そのほかにも色々とありますが代表的なものを掲載しておきましょう。
\d
バックスラッシュが付いたdです。これは[0-9]に相当します。
\D
D(大文字)になると[^0-9]と等価です。要するに数字以外にマッチします。
※こういったバックスラッシュと組み合わされたメタキャラクタと言うものはPerl以降の実装であり、sed等の古くある実装では見られないものです。一般的な処理系はこれ見本に実装されていますが、中には標準的なものに拡張等を施した為に若干の差異を生じているものも存在します。例えば\dは通常[0-9]と同じ様に半角数字がマッチするのですが、InDesignの正規表現エンジンはU+ff10~U+ff19(全角数字)のキャラクタもヒットさせます。この様に同じ正規表現であっても処理を行うアプリケーションによってこの様な違いがある場合があります。
数で指定する
日常的に編集作業をやっていると2個の数字の連続を何かしたいというケースが出てきます。こう言った数で制御する方法が以下の正規表現です。
まず基本形です。
a{2,4}
aに続いてブレースで2つの数字がコンマで区切られて存在します。この表現はaが2〜4個連続する文字列となります。aaとかaaaとかaaaaにマッチすると言う事になります。
このブレースにはさまれた数字の書き方で若干動作を変えられます。
a{2}
こちらはaが2個の連続です。aaにマッチします。
a{2,}
続いてのこちらはカンマの後の数字が省略されています。こう記述すると2個以上のaの連続となります。
行単位で処理する
正規表現では行単位で文字列を扱うのに便利なメタキャラクタも存在します。
まずは以下を見てみます。
^abc
「^」がブラケットの中に入っていません。こうすると行の先頭からabcとなります。要するに行頭を示す為のメタキャラです。行頭があれば当然のごとく行末もあります。
abc$
サンプルに芸がありませんがこれで行末がabcの場合にマッチします。
続いて、よく見かける例を見てみます。
^$
この例では行頭と行末をくっつけてます。予想がつくかと思いますが空改行の行にマッチします。
^.*$
行頭と行末の間に任意の文字の連続…と書くと非常にややこしいのですが、どんな行にでもマッチする表現です。文字列を一行づつ取り出したい場合に重宝するパターンです。
hogeもしくはfuga
さて、選択肢が一文字の場合はブラケット内に列記すれば選択できました。今度は単語自体を選択肢として使いたい場合です。
hoge|fuga
出てきたのは「|」です。選択肢を「|」で挟んだだけです。
例えば
hoge|hogehoge|fuga|fugafuga
などとなっている場合、hogeかhogehogeまたはfugaまたはfugafugaがマッチします。
グルーピング
概念的にはそう難しく無いのですが、文字にするととてもややこしくなる類のものです。任意の正規表現を最小単位として扱う事をグルーピングって言います。日本語が分かりにくいですね。以下を見てみます。
(abc|def) \d{2,5}
「(」と「)」で括られた正規表現は、一つの塊(グループ)として扱われます。この例ではabcかdefで始まり数字が2〜5個つづく文字列にマッチします。長ったらしいリストから特定の製品番号等に対して処理を行う場合によく利用されるパターンです。この様に()で括る事により任意の正規表現パターンを一つの文字と等価な表現とすることができます。
例えば
(Adobe\s)?InDesign(\sCC)?
という書き方をすると「Adobe InDesign」「InDesign」「Adobe InDesign CC」「InDesign CC」にマッチします。
最短一致
ここまでの正規表現ではできるだけ長いマッチ部分を検索しようとします。例えば\d+と書くと「0123456789」の文字列が与えられた場合、全体がマッチしてしまいます。しかしながら、仕事においては逆に短い部分を抽出したい場合というのが多々あります。では、その切替というのはどのようにするかというと数量子に「?」をつけるだけ。
\d+?
こうなります。この場合、先ほどの文字列だとマッチするのは先頭の「0」だけということになります。
先読みと後読み
ある条件を付けて続く文字を検索することって良くあると思いますが、その条件自体はひっかけたくない場合というのも多々ありますが、そう言う場合に威力を発揮するのが後読み先読みの機能です。
ごちゃごちゃ書いてますが、例を見た方が早いと思われるので
否定後読み(?<!)
マッチさせたい物に対して先行して記述します。
(?<!\d)\d\d
これは2桁以上の数字の頭2桁にマッチします。(?<!\d)で数字以外の文字との境界を検出しています。
肯定後読み(?<=)
先ほどとは逆のパターンです。以下の様にすると…
(?<=\d)\d\d
3桁以上続く数字の2桁目と3桁目にマッチします。
肯定先読み(?=)
後読み・先読みとなっていますがマッチさせたい対象が前か後かという意味になっています。ですから、先読みの場合()でグルーピングする見るだけの部分が後ろに来ることにご注目下さい。
\d\d(?=\d+)
この例は3桁以上続く数字の1桁目と2桁目にマッチします。
否定先読み(?!)
先ほどとは逆に否定となります。
\d\d(?!\d+)
こちらだとおわりの2桁の数字となります。
これらを組み合わせて2桁の数字のみの抽出には以下のような表現を使います。
(?<!\d)\d\d(?!\d+)
最後に
ここに書いてきたものは初歩的な内容ですが、実践的に利用するに充分なものを含めてあります。しかしながらInDesign等の独自の実装は省略してあります。また、ExtendScriptを始めJavaScriptの実装では先読み・後読みはサポートされていないことに注意が必要です。