POSIX正規表現 regex.h

Cの言語仕様そのものには正規表現は含まれていないので、C言語で正規表現を扱うには何らかのライブラリを利用する必要があります。ここでは、POSIX準拠のregex.hを利用して正規表現の処理を行います。

UNIXライクなOSなど、POSIX準拠のシステムであれば使用できるはずです。このページのサンプルなどはLinux上のgcc-2.96でテストを行っています。

使用方法

regex.hでの正規表現の基本的な使用方法を簡単に述べると、次のようになります。

  1. regcomp()で正規表現をコンパイル
  2. regexec()で実行
  3. regfree()でコンパイルした正規表現を解放

多少面倒なように見えますが、正規表現ライブラリとしてはごく一般的な手順です。正規表現による検索処理の前に正規表現の文字列をコンパイルし、検索処理を行った後に処理に使ったバッファを解放する必要があります。

ということで、関数をひとつずつ見ていきましょう。

regcomp()

regcomp()
int regcomp(regex_t *preg, const char *regex, int cflags)
POSIX正規表現関数/regex.h
引数
  1. 正規表現パターンバッファpreg
  2. 正規表現文字列regex
  3. コンパイルフラグcflag
機能正規表現パターンをコンパイルする
返却値コンパイルが成功すると、0を返す。失敗した場合には、エラーコードを返す。
注意詳細はregcompのマニュアルページを参照。

regexパターンバッファは、たとえば次のように宣言します。

regex_t reg;

regexec()

regexec()
int regexec(const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags);
POSIX正規表現関数/regex.h
引数
  1. コンパイルされた正規表現パターンバッファpreg
  2. 検索対象文字列string
  3. 一致した位置の格納数nmatch
  4. マッチした位置の格納用配列pmatch[]
  5. マッチング動作フラグeflags
機能正規表現による検索処理を実行する
返却値正規表現によるマッチングの成功時には0を返す。失敗した場合には、REG_NOMATCHを返す。
注意詳細はregcompのマニュアルページを参照。

nmatchについて

上の関数の説明ではnmatchは一致した位置の格納数と書いてありますが、実はLinux上のgccのregex.hで試した結果、文字列中に複数のマッチすべきパターンがあっても、最初の1個目の位置情報しか格納されないようです。これは、Manpage of REGEXの以下の節の理由によるようです。

正規表現が、与えられた文字列の複数の部分文字列 (substring) にマッチできるような場合には、最も先頭の近くから始まるものにマッチする。その位置から始まり、正規表現がマッチできる部分文字列が複数ある場合には、最長のものにマッチする。部分正規表現 (subexpression) も最も長い部分文字列にマッチする。ただし、全体のマッチが最長であるように、という条件が優先される。正規表現の中で先に現れる部分正規表現は、後に現れるものより優先される。ただし、より高位の部分正規表現は、それを構成する低位の部分正規表現よりも優先されることに注意すること。

この件について掲示板でご指摘を頂きました。内容を引用します(後でもう少し詳しく書きます)。

Name: 2323 日時: 11/30(Tue.) 16:59

こんにちは。 URL:http://sometime.minidns.net/~ccgi/posix_regex.html が検索でひっかかりました。このWebページ中

「文字列中に複数のマッチすべきパターンがあっても、最初の1個目の位置情報しか格納されないようです」 との記述がありますが、これは同じパターンでマッチする場所が複数ある場合に使うのではなく、'('と')'のペアでatomを作った場合に()の数だけ追加で入ってくるものです。このWebページ、google日本語でregcompとかやると結構上位に引っかかるので、記述を修正された方が良いかと。

2323さんどうもありがとうございました。

とりあえずサンプルを追加しました。

regmatch_t

regexec()はコンパイル済みの正規表現パターンによる検索処理を行います。この関数の4番目の引数は、regmatch_tの配列ということになっています。GCCでは、regmatch_tは構造体で、次のようにtypedefされています。

typedef struct
{
    regoff_t rm_so;
    regoff_t rm_eo;
}regmatch_t;

regoff_tというのは何かというと、regex.hを見たところ、

typedef int regoff_t;

と定義されていました。要するにintです。regmatch_t構造体のメンバrm_soは、文字列中で正規表現にマッチしたパターンのスタート位置の、文字列の先頭からのオフセットの値になります。また、rm_eoは、文字列中で正規表現にマッチしたパターンの終了位置(正確にはその次の文字)の、文字列の先頭からのオフセットの値になります。推測ですが、これらのメンバ名はregmatch_start offsetとregmatch_end offsetのことだと思われます。

正規表現による検索がマッチしなかった場合、regmatch_t構造体のメンバには、それぞれ-1が代入されます。

regerror()

正規表現にミスがあるかどうかを確認したいときには、regcomp()やregexec()の返却値であるエラーコードを使用します。これらの関数は(符号付きかなしかはともかく)int型なので、エラーコードとして返ってくる値は整数値です。regcompのマニュアルページにあるように、エラーコードはREG_BADRPTなどの定数値マクロとして定義されています。エラーコードが数値で得られても、数値から対応するマクロの識別子を直接知ることはできません。

エラーコードとの対応をひとつずつ調べるという方法もありますが、regerror()を使用すると、エラーの内容を文字列で得ることができます。

regerror()
size_t regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size);
POSIX正規表現関数/regex.h
引数
  1. regcomp()かregexec()の返却値
  2. 正規表現パターンバッファpreg
  3. 文字列バッファの先頭へのポインタerrbuf
  4. errbufへの文字列の最大格納数errbuf_size
機能regcomp()とregexec()の返却値からエラーメッセージの文字列を得る
注意詳細はregcompのマニュアルページを参照。

regerror()サンプル

regerror()を利用してエラーメッセージを得るサンプルです。

#include <stdio.h>
#include <regex.h>

int main(void)
{
    regex_t reg; /* regexパターンバッファ */
    regmatch_t match; /* マッチした場所格納用 */
    char errbuf[100]; /* エラーコードの文字列格納用バッファ */
    char comment[] = "abcdefghij";
    printf("元の文字列:\n%s\n\n",comment);

    regerror(regcomp(&reg,"[ijk",REG_EXTENDED),&reg,errbuf,sizeof errbuf);
    printf("regcompエラーコード:%s\n",errbuf);
    regerror(regcomp(&reg,"ijk",REG_EXTENDED),&reg,errbuf,sizeof errbuf);
    printf("regcompエラーコード:%s\n",errbuf);

    regerror(regexec(&reg,comment,1,&match,0),&reg,errbuf,sizeof errbuf);
    printf("regexecエラーコード:%s\n",errbuf);

    regfree(&reg);
    return 0;
}

実行結果は次のようになります。

元の文字列:
abcdefghij

regcompエラーコード:Unmatched [ or [^
regcompエラーコード:Success
regexecエラーコード:No match

regfree()

regfree()
void regfree(regex_t *preg);
POSIX正規表現関数/regex.h
引数正規表現パターンバッファpreg
機能正規表現パターンバッファを解放する

POSIX正規表現のいくつかの例

詳細なマニュアルはManpage of REGEXを見てください。ここでは、使用頻度の高そうな一部の例のみ解説します。

繰り返し

'*'、'+'は直前の要素(文字または()で囲まれた文字列等)の繰り返しを指定します。'*'は0回以上の繰り返し、'+'は1回以上の繰り返しにマッチします。'?'は直前の要素の0回または1回にマッチします。

任意の回数の繰り返しは、要素の後に{i,j}のように指定します。{i,j}でi回以上j回以下の繰り返し、{i,}とするとi回以上の繰り返し、{i}とするとi回の繰り返しに対応します。

ブラケット表現

'['と']'で文字のリストを囲むと、そのリスト中に存在している文字にマッチします。リストの最初の文字が'^'の場合、そのリスト中に存在していない文字にマッチします。

[0-9]や[a-z]のような指定方法も可能ですが、通常はそのような場合は次の文字クラスを用いて指定します。

文字クラス

アルファベットや数字といった文字すべてを表す文字クラスには、alphaやdigitなどがあります。isalpha()やisdigit()といったANSIで規定されているis〜()関数の〜の部分はすべてあります。その他、blankがあります。

文字クラスは、'[:'と':]'で囲んで指定します。実際にはブラケット表現の内部で使用するので、たとえばアルファベットのすべてを指定する場合には、"[[:alpha:]]"のようになります。

サンプル

#include <stdio.h>
#include <regex.h>

int main(void)
{
    int regsuccess; /* パターンマッチングの成功フラグ */
    int i,nmatch=5;
    regex_t reg; /* regexパターンバッファ */
    regmatch_t match[nmatch]; /* マッチした場所格納用 */

    char* str = "てすとhttp://next.mokp.com/~mockekke/abc.cgi?mode=count,"
        "http://123.456.789.1/~mockekke/abc.cgi "
        "http://sometime.minidns.net/~mockekke/てすと";

    regcomp(&reg,"http://[[:alnum:]./~?&%#_=-]+",REG_EXTENDED);
    regsuccess = regexec(&reg,str,nmatch,match,0);
    regfree(&reg);

    printf("元の文字列:\n%s\n\n",str);

    for(i=0;i<nmatch;i++) printf("%d,%d\n",match[i].rm_so,match[i].rm_eo);
    for(i=match[0].rm_so;i<match[0].rm_eo;i++) putchar(str[i]);
    putchar('\n');

    return 0;
}

サンプルの実行結果

適当に改行を入れています。

元の文字列:
てすとhttp://next.mokp.com/~mockekke/abc.cgi?mode=count,http://123.456.789.1
/~mockekke/abc.cgi http://sometime.minidns.net/~mockekke/てすと

6,55
-1,-1
-1,-1
-1,-1
-1,-1
http://next.mokp.com/~mockekke/abc.cgi?mode=count

サンプルその2(部分正規表現)

#include <stdio.h>
#include <regex.h>

int main(void)
{
    int regsuccess; /* パターンマッチングの成功フラグ */
    int i, j, nmatch=5;
    regex_t reg; /* regexパターンバッファ */
    regmatch_t match[nmatch]; /* マッチした場所格納用 */

    char *str = "てすとhttp://next.mokp.com/~mockekke/abc.cgi?mode=count,"
        "http://123.456.789.1/~mockekke/abc.cgi "
        "http://sometime.minidns.net/~mockekke/てすと";

    regcomp(&reg,"(http):(//)([[:alnum:]./~?&%#_=-]+)",REG_EXTENDED);
    regsuccess = regexec(&reg,str,nmatch,match,0);
    regfree(&reg);

    printf("元の文字列:\n%s\n\n",str);

    for(i=0;i<nmatch;i++) printf("%d,%d\n",match[i].rm_so,match[i].rm_eo);
    for(j=0; j<nmatch; j++) {
        if(match[j].rm_so>=0 && match[j].rm_eo>=0)
            for(i=match[j].rm_so;i<match[j].rm_eo;i++) putchar(str[i]);
        putchar('\n');
    }

    return 0;
}

サンプルその2実行結果

元の文字列:
てすとhttp://next.mokp.com/~mockekke/abc.cgi?mode=count,http://123.456.789.1/~mo
ckekke/abc.cgi http://sometime.minidns.net/~mockekke/てすと

6,55
6,10
11,13
13,55
-1,-1
http://next.mokp.com/~mockekke/abc.cgi?mode=count
http
//
next.mokp.com/~mockekke/abc.cgi?mode=count

上の例では、"(http):(//)([[:alnum:]./~?&%#_=-]+)"のように、正規表現の一部を括弧で囲み、atomとして部分正規表現を作っています。こうして作ったatomは、regmatch_t構造体の配列に、nmatchで指定した数だけマッチした場所が格納されます。

実行結果を見るとわかるように、regmatch_t構造体の配列の先頭の要素には、正規表現全体がマッチした範囲が格納されています。nmatchが2以上の場合、atomにマッチした位置が先頭から順に格納されます。この場合、正規表現にマッチした文字列のうち、"http"、"//"という順に、先頭から数えた開始の位置と終了の位置が格納されていることがわかると思います。


Index Page