― .NET Frameworkがサポートする正規表現クラスを徹底活用する ―:基礎解説 スマートな文字列処理のための 正規表現入門(前編)(4/4 ページ)
正規表現をうまく使いこなせば、数十行のコードにも匹敵するテキスト処理をたった数行で実現することも可能だ。今回はまず、正規表現の基礎について解説する。
■量指定子(*、+)
先ほどはコメント行(「//」を含む行)を検索する例を示したが、今度はコメントだけが記述された行を見つけることにしよう。このままの表現ではずいぶん難しく感じられるかもしれないが、考え方を変えてみれば単純になる。「//」形式のコメントは、行末までがコメントとして扱われる。ということは、「//」よりも前に何もなければ、行全体がコメントということになる。何もないといっても、スペースやタブのようにプログラムに影響を及ぼさないキャラクタはあってもよいこととする。すると、「コメントだけが記述された行」にマッチするパターンは以下のように表現できる。
regex "^[ \t]*//" regex.cs
/// <summary>
/// アプリケーションのメイン エントリ ポイントです。
/// </summary>
// ファイル名が指定されなかったときは標準入力から
// 正規表現はRegexインスタンスに対して固定
“^”は行頭にマッチするメタ文字。“[ \t]”はスペースかタブ文字にマッチするメタ文字。ここまでは既出だが、次の“*”は新出のメタ文字だ。このメタ文字の意味は「直前の要素(あるいは1文字)の0個以上の連続にマッチする」である。つまり前述の例の“[ \t]*//”ならば、「^//」や「^ //」、「^ \t\t\t\t//」などにマッチするパターンとなる。
メタ文字“*”を使うときには、直前の要素が1文字も存在しなくても、マッチしてしまうことに注意が必要だ。例えば、タブの連続の後に英字が連続する文字列を検索しようとして、以下のようなパターンを指定したとしよう。結果はどうなるだろうか。
regex "\t*[a-z]*" regex.cs
この検索ではregex.csのすべての行が出力されてしまう。
もしかしたら予想とは異なるかもしれないが、このパターンではregex.csのすべての行が出力されてしまう。なぜなら、メタ文字“*”は、0文字の文字にもマッチするからだ。0文字とは「無くても構わない」という意味でもあるし、「空文字列にマッチする」という意味でもある。上記のように「0文字以上のタブと、0文字以上の英字」を検索すると、完全な空文字列にもマッチすることになってしまう。空文字列はどこにでもあると解釈できるので、結局すべての行にマッチしてしまうのである。
もし0文字の場合は除外して、1文字以上の連続にマッチさせたい場合は、“*”の代わりに“+”を指定すればいい。こうすれば、タブも英字も少なくとも1文字は存在しなければマッチしなくなるため、すべての行が出力されるようなことはなくなる。
regex "\t+[a-z]+" regex.cs
static void Main(string[] args) {
try {
if (args.Length < 1) {
reader = Console.In;
reader = new StreamReader(
args[1], System.Text.Encoding.GetEncoding(932));
if (reader != null) {
string line;
while ((line = reader.ReadLine()) != null) {
if (m.Success) { // 一致すればtrue
reader.Close();
catch (Exception e) {
この“*”や“+”のように、直前の要素を繰り返し適用し、検索を指示するメタ文字を「量指定子」と呼ぶ。量指定子にはこのほかにも、指定した回数だけ適用を指示する“{ 〜 }”などが用意されている(例:(\.\d+){3})。量指定子は使いこなしが難しいメタ文字だが、うまく使いこなせば複雑な文字列でも、比較的短い正規表現でマッチさせることができるようになる。正規表現のマスターは量指定子のマスターであるといっても過言ではない。
ところで、量指定子とは直接関係ないが、前述の「コメントだけを含む行」を検索するパターン“^[ \t]*//”は、以下のように書き換えることもできる。“[ \t]”が“\s”で書き換えられているわけだ。
regex "^\s*//" regex.cs
厳密にいえば両者は同じではないが、“\s”は一般的に空白文字と見なされるスペースやタブなど、幾つかの文字に一致する「文字クラス」として利用できる。この種の文字クラスには“\s”以外にも、10進数字と一致する“\d”([0-9]と同意)や、単語に使用される任意の文字と一致する“\w”(英数字、アンダースコアなど)などが用意されている。
■ピリオド
今度は、コメント内に文字列「<summary>」を含むコメント行を見つけるためのパターンを考えてみよう。コメント行を見つけるだけならば「//」を検索すればいいし、「<summary>」だけを検索するのも簡単だ。しかし、「//」の後ろのどこかに「<summary>」があるパターンはどうやって表現したらいいのだろうか。これは、メタ文字“.”(ピリオド)を使って、以下のように表現できる。
regex "//.*<summary>" regex.cs
/// <summary>
ピリオドは「任意の1文字にマッチする」メタ文字である(唯一“\n”にはマッチしない)。万能型の“[ 〜 ]”だと考えればいいだろう。従って、“.*”は「何でもいいから0文字以上の文字」にマッチするパターンを意味することになる。このピリオドとアスタリスクのペアは非常に使い勝手がいいので覚えておこう。
ところで、正規表現をサポートしたツールの中には、“<”と“>”が単語の区切り目を表すメタ文字として扱われる場合があるが、.NET Frameworkのクラス・ライブラリでは、この用途に“\b”が用いられるので、不等号はメタ文字でなく通常の文字として扱われる。
■?
C#のコメントとして記述するXMLドキュメント(クラスやメソッドについてのドキュメントを「///」に続けてXMLデータを記述する)では、必ず開始タグと終了タグをペアで使用しなければならない。そのため、オブジェクトの要約を解説するために<summary>タグを利用するときは、必ずペアとなる</summary>タグがあるはずだ。そこで、今度はコメントに<summary>か</summary>が含まれている行を見つけるパターンを考えてみよう。
これは、前述のパターンにメタ文字“?”を追加するだけで簡単に表現できる。
regex "//.*</?summary>" regex.cs
/// <summary>
/// </summary>
“?”は「直前の要素(または1文字)が0個または1個あるときにマッチする」メタ文字である。“*”の機能を少し弱めたようなメタ文字で、2文字以上の連続にはマッチしない。この例ならば、「スラッシュがあってもなくてもマッチする」ことになる。
■( | )
それでは前編の最後に、orを意味するメタ文字“|”(バーチカルバー)と、グループ化を行う“( 〜 )”を解説しよう。
「いずれか1文字」を意味するメタ文字には“[ 〜 ]”があったが、これは文字単位のorなので、「aかb」は表現できても「aaaかabc」を表現することはできない。こうした文字列単位のorを表現するために利用するメタ文字が“|”である。例えば、「ifかwhileを含む行」を見つけたいのであれば、以下のように「if」と「while」を“|”でつなげたパターンで表現できる。
regex "if|while" regex.cs
if (args.Length < 1) {
} else if (args.Length == 1) {
if (reader != null) {
while ((line = reader.ReadLine()) != null) {
if (m.Success) { // 一致すればtrue
それでは、「条件判断の式に変数readerが使われているif文かwhile文」を見つけるにはどうしたらいいだろう。「ifかwhile」の部分を「|」で表現できるのは変わらない。ただ、「if|while.*reader」などとしてしまっては、「if、またはwhileにreaderが続く」行を表現するパターンになってしまう。“|”はその左右のパターンをorで結ぶだけだからだ。“|”の効果範囲を限定したければ、次のように“(”と“)”でパターンを囲み、グループ化する必要がある。
regex "(if|while).*\(.*reader.*\)" regex.cs
if (reader != null) {
while ((line = reader.ReadLine()) != null) {
こうすれば、正しく「条件判断にreaderが使われているif文かwhile文」を見つけることができる。
この“( 〜 )”は“|”の効果範囲を限定する以外に、“*”や“+”、それに“?”といった量指定子の適用範囲をグループ化するためにも利用される。量指定子をそのまま使うと、直前の1文字、または直前の要素にのみ適用されてしまうので、“a+”で「aaaa」にマッチさせることはできても、「abcabc」にマッチさせるパターンは表現できない。そこで括弧を“(abc)+”のように使ってグループに対して適用されるように指示するのである。括弧の中にはもちろん通常文字だけでなく、正規表現を指定できるので、以下のようにしてメンバ・アクセス式などのピリオドで連結されている単語を検索できる。
regex "\w+(\.\w+)+" regex.cs
using System.Text.RegularExpressions;
using System.IO;
if (args.Length < 1) {
Console.Write("regex <RegularExpression> [<filesname>]\n");
} else if (args.Length == 1) {
reader = Console.In;
args[1], System.Text.Encoding.GetEncoding(932));
while ((line = reader.ReadLine()) != null) {
Match m = regex.Match(line); // lineから検索
if (m.Success) { // 一致すればtrue
Console.WriteLine(line);
reader.Close();
System.Console.WriteLine(e.Message);
以上で正規表現の基礎的なルールの解説は完了だ。次回後編では、これらの正規表現パターンを用いたキャプチャ(部分文字列の抜き出し)や、部分文字列の置換などについて解説する。
冒頭で紹介した正規表現の答え:
いわゆるCSV形式のデータ行にマッチする正規表現。各フィールドの値をキャプチャするための仕掛けも含まれている。
Copyright© Digital Advantage Corp. All Rights Reserved.