株式会社antsのホームページへようこそ。

初心者のための正規表現入門<第1回>

0
Posted in Lab. By nishihira

プログラムを書いていて、文字列の処理困ったことはありませんか? 例えば次のような処理が必要になるケースがよくあるかと思います。文字列の中から特定の文字列を探したり、置き換えをしたい、ということが。そこで今回から、このような処理に適した伝統ある解決法「正規表現」について書いてみます。

題して「初心者のための正規表現入門」です。

ひとつ、典型的な例題を考えてみましょう。

例題:

  1. ある文章中に出現するMicrosoft Windows 2000以降を表す文字列(「Microsoft Windows 2000」といった文字列)を、すべて「Microsoft Windows Vista」に置き換えてほしい。
  2. ただし単語の表記がまちまちになっているので注意したい。具体的には、大文字/小文字の表記やスペースの有無が同じであるとは限らない。(「MicroSoft Windows 2000」「microsoftwindows2000」「MICRO SOFT WINDOWS 2000」などまちまちになっている)

こんなとき、あなたはどうしますか? 文字を扱える言語であればたいてい標準の関数で「n番目の文字を取り出す」というような関数が用意されていますから、それを使うことをまず始めに思い浮かべるかもしれません。また言語によっては、「文字列の中に、Xという文字列が含まれるかどうかをチェックし、含まれる場合はその位置を返す」とか「文字列の中にXという文字列が見つかった場合、Yという文字列に置き換える」といった関数が用意されているかもしれません。

こういった関数を使って文字列処理を行うとしたら、こういうロジックになるかもしれません。

 回答例:「n番目の文字を取り出す」関数を使って上記の処理を行う場合

  • 文章の頭から、順次1文字づつ読んでいく
    • その文字が「M」または「m」だった場合のみ以下のチェックを行う
      • その次の文字が「I」または「i」だった場合のみ以下のチェックを行う
        • その次の次の文字が「C」または「c」だった場合のみ以下のチェックを行う
          • その次の次の次の文字が「R」または「r」だった場合のみ以下のチェックを行う
            • その次の次の次の次の文字が「O」または「o」だった場合のみ以下のチェックを行う
              • その次の次の次の次の次の文字が「S」または「s」だった場合のみ以下のチェックを行う。ただしこの文字がスペース文字だった場合は読み飛ばし、次の文字をつかって「S」or「s」のチェックを行う
                • その次の次の次の次の次の文字が……(略)
                • (中略)
                  • その次の(中略)次の文字が「0」だった場合は見事に「Microsoft Windows 2000」を表す文字列にマッチしたものと見なし、「Microsoft Windows Vista」という文字列と置き換える

まあ単純なロジックではありますが、非常に面倒なコーディングになりそうですね。「Xという文字列の位置を返す」関数を使うことができるのであれば、多少は楽になりそうです。しかし、大文字/小文字やスペース有無のバリエーションがあるので、やはり似たように何段階に分けてのチェックが必要になるでしょう。

「文字列の中にXという文字列が見つかった場合、Yという文字列に置き換える」という関数を使うと、もっと簡単にこの問題を解決できそうな気がします。しかし「Xという文字列が見つける」判定が「完全一致」である場合は、この方法を使うことはできません。

チェックの部分をサブルーチン化したり、ロジックを巧妙にすることで、ある程度汎用的でシンプルなコーディングにすることは可能かと思います。しかし、ルールが増えた場合はどうでしょう。「Microsoft Windows 2000」のほかに「Microsoft Windows XP」も置き換えの対象に含めるとしたら? あるいは「Microsoft Windows 2000 Server」は例外として置き換え対象外にするとしたら?

ルールが複雑になる度にコーディングもどんどん複雑になってしまい、やがてはメンテナンスが不可能なソースコードになってしまうのではないでしょうか。

こんな時に正規表現を使いましょう

コーディングの勉強のために上記のような課題に挑戦してみることは良いことかもしれませんが、普段の仕事で使うプログラムでは、こうしたその場限りのコードを書くのではなくなるべく汎用的な解決方法を使いたいものです。このような「文字列の検査/置換」における汎用的な解決方法として、「正規表現」というものがよく使われます。具体的なコード例として、上記の例題を解決するためのコードをJavaScriptで書いてみましょう。

置換スクリプト.js

var text = "クライアントPCのOSは、Microsoft Windows 2000と、MicroSoft Windows 2000と、\n" +
  "microsoftwindows2000と、MICRO SOFT WINDOWS 2000と、MICRO SOFT WINDOWS XPです。\n" +
  "サーバーマシンのOSはMicrosoft Windows 2000 Serverです。";
replaceText = text.replace(/micro\s*soft\s*windows\s*(2000|XP)(?!\s*server)/ig,"Microsoft Windows Vista");
alert(replaceText);

たったのこれだけで上記例題のルールに基づいたの置き換えを行うことができます。このサンプルコードでは、JavaScriptのreplace関数という正に「文字列の中にXという文字列が見つかった場合、Yという文字列に置き換える」(String.replace(X,Y))という関数を使っていますが、replace関数の文字列チェックは正規表現にも対応しているため、このようなシンプルなコードで上記のような文字列置き換え処理を行うことができます。

この関数の中の1つめの引数、

/micro\s*soft\s*windows\s*(2000|XP)(?!\s*server)/ig

の部分が正規表現です。

このように文字列を検査する「ルール」を記述することで、そのルールに当てはまるすべての文字列を置き換えの対象として扱ってくれるのです。この「ルール」を記述するための文法をマスターすることが、正規表現を使いこなすために必要なことのすべてです。例えば上のような場合、以下のような文法を使って検査を行っています。

  • 1つめの「/」と2つめの「/」に挟まれた文字列を検査条件とします。
    例えば「/Microsoft/」と書かれていた場合は、「Microsoft」という文字列を検査対象とします。
  • 「\s*」は、「スペース文字または長さ0のブランク」を表します。
  • 「(2000|XP)」は、「『2000』または『XP』」という文字列を表します。
  • 「(?!\s*server)」の部分では、検査対象外の条件を指定しています。

    この場合は「『2000』または『XP』という文字列の後に、(スペース文字または長さ0のブランクを挟んで)『server』という文字列が続いた場合は、例外として検査対象外とする」という意味になります。

  • 2つめの「/」の後の「ig」は、正規表現のオプション指定です。
    • 「i」は「大文字小文字の違いは無視する」というオプションです。
    • 「g」は、「検査対象の文字列中で複数回検査にマッチした場合、そのすべてを処理対象とする」というオプションです。

そしてこの検査にマッチした文字列を全て、replace関数の二つめの引数「Microsoft Windows Vista」と置き換えます。これが正規表現の典型的な使い方です。ほかにも、いろいろなパターンに対応するための文法が正規表現には用意されています。それらの文法については次回以降の連載で詳しく解説していく予定です。