C標準ライブラリには正規表現関連の機能は含まれていないので、C言語で正規表現を扱うには何らかのライブラリを利用する必要があります。ここでは、POSIX準拠のregex.hの使用方法を解説します。
UNIXライクなOSなど、POSIX準拠のシステムであれば使用できるはずです。このページのサンプルの確認には、LinuxまたはFreeBSDとgccを使用しています。
regex.hでの正規表現の基本的な使用方法を簡単に述べると、次のようになります。
多少面倒なように見えますが、正規表現ライブラリとしてはごく一般的な手順です。正規表現による検索処理の前に正規表現の文字列をコンパイルし、検索処理を行った後に処理に使ったバッファを解放する必要があります。
まずはregex.hの関数の仕様をひとつずつ見ていきましょう。
正規表現を使用した検索を行うには、事前に正規表現パターンをコンパイルしておく必要があります。正規表現パターンをコンパイルするには、関数regcompを使用します。
| int regcomp(regex_t *preg, const char *regex, int cflags) | |
|---|---|
| POSIX正規表現関数/regex.h | |
| 引数 |
|
| 機能 | 正規表現パターンをコンパイルする |
| 返却値 | コンパイルが成功すると、0を返す。失敗した場合には、エラーコードを返す。 |
| 注意 | 詳細はregcompのマニュアルページを参照。 |
実際のパターンマッチングは、関数regcompによってコンパイルされたパターンバッファを関数regexecに渡すことで行います。この関数の動作は、コンパイル時に関数regcompに対して指定したフラグの影響を受けます。
| int regexec(const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags); | |
|---|---|
| POSIX正規表現関数/regex.h | |
| 引数 |
|
| 機能 | 正規表現による検索処理を実行する |
| 返却値 | 正規表現によるマッチングの成功時には0を返す。失敗した場合には、REG_NOMATCHを返す。 |
| 注意 | 詳細はregcompのマニュアルページを参照。 |
正規表現にミスがあるかどうかを確認したいときには、regcomp()やregexec()の返却値であるエラーコードを使用します。これらの関数は(符号付きかなしかはともかく)int型なので、エラーコードとして返ってくる値は整数値です。regcompのマニュアルページにあるように、エラーコードはREG_BADRPTなどの定数値マクロとして定義されています。エラーコードが数値で得られても、数値から対応するマクロの識別子を直接知ることはできません。
エラーコードとの対応をひとつずつ調べるという方法もありますが、regerror()を使用すると、エラーの内容を文字列で得ることができます。
| size_t regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size); | |
|---|---|
| POSIX正規表現関数/regex.h | |
| 引数 |
|
| 機能 | regcomp()とregexec()の返却値からエラーメッセージの文字列を得る。 |
| 注意 | 詳細はregcompのマニュアルページを参照。 |
正規表現パターンをコンパイルするための関数regcompは、関数内でメモリを確保します。そのため、不要になった正規表現パターンバッファは開放しておく必要があります。
| void regfree(regex_t *preg); | |
|---|---|
| POSIX正規表現関数/regex.h | |
| 引数 | 正規表現パターンバッファへのポインタ |
| 機能 | 正規表現パターンバッファを解放する。 |
詳細なマニュアルは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:]]"のようになります。
まず、関数regerrorを用いてエラーメッセージ文字列を得る例を示します。パターンバッファのコンパイル結果は役に立つ情報なので、実際のプログラムではregcomp()の返却値を取得するようにしておいた方が良いでしょう。
#include <stdio.h>
#include <regex.h>
int main(void)
{
regex_t reg;
regmatch_t match;
char errbuf[100];
char comment[] = "abcdefghij";
printf("元の文字列: %s\n\n", comment);
regerror(regcomp(®, "[ijk", REG_EXTENDED), ®, errbuf, sizeof errbuf);
printf("regcompエラーコード: %s\n", errbuf);
regerror(regcomp(®, "ijk", REG_EXTENDED), ®, errbuf, sizeof errbuf);
printf("regcompエラーコード: %s\n", errbuf);
regerror(regexec(®, comment, 1, &match, 0), ®, errbuf, sizeof errbuf);
printf("regexecエラーコード: %s\n", errbuf);
regfree(®);
return 0;
}
実行結果は次のようになります。
元の文字列: abcdefghij regcompエラーコード: Unmatched [ or [^ regcompエラーコード: Success regexecエラーコード: No match
文字列に正規表現パターンにマッチする部分があるとき、それがどの部分なのかがわかると便利です。regexec()の3番目と4番目の引数はその目的で使用します。4番目の引数は、regmatch_t型オブジェクトへのポインタです。regmatch_tは構造体で、次のようにtypedefされています。
typedef struct
{
regoff_t rm_so;
regoff_t rm_eo;
}regmatch_t;
regmatch_t構造体のメンバの型regoff_tはバイトオフセットを表現するための抽象化された型で、文字列の先頭からのオフセットを表す整数です。rm_soは「パターンにマッチした部分文字列のスタート位置」の、文字列の先頭からのオフセットの値です。また、rm_eoは、「パターンにマッチした部分文字列の終了位置」(正確にはその次の文字)の、文字列の先頭からのオフセットの値です。推測ですが、これらのメンバ名はregmatch_start offsetとregmatch_end offsetの略だと思われます(まだ略してるって? 気のせいです)。
regexec()は、4番目の引数としてregmatch_t型オブジェクトへのポインタを受け取ります。実引数として単なるregmatch_t型オブジェクトへのポインタを渡しても構いませんが、regmatch_t型オブジェクトの配列の先頭要素へのポインタを渡し、3番目の実引数として、配列の要素数か、要素数以下の整数値を渡すこともできます。
コード例を示します。
#include <stdio.h>
#include <regex.h>
void put_n_char(const char *s, regoff_t so, regoff_t eo)
{
const char * const ep = s + eo;
for(s += so; s != ep; s++) putchar(*s);
}
int main(void)
{
regex_t reg;
const char *pattern = "h(ttp:(//))([[:alnum:]./]+)";
regmatch_t match[5];
int match_elems = sizeof match / sizeof match[0];
char *s[] = {
"http://www.example.com",
"123http://www.google.com/",
};
int s_elems = sizeof s / sizeof s[0];
int i, j;
regcomp(®, pattern, REG_EXTENDED);
for(i = 0; i < s_elems; i++) {
if(regexec(®, s[i], match_elems, match, 0) != REG_NOMATCH) {
for(j = 0; j < match_elems; j++) {
// 注: 実装によっては regoff_t は単純な int でない可能性がある
printf("%2d, %2d: ", match[j].rm_so, match[j].rm_eo);
if(match[j].rm_so >= 0)
put_n_char(s[i], match[j].rm_so, match[j].rm_eo);
puts("");
}
}
puts("");
}
regfree(®);
return 0;
}
コメントがC++/C99スタイルになっていますが、C99以前からこの拡張を使えないコンパイラは少ないはずなので、「これは正当なCじゃない!」とか言わないでください(そんな人がいるとは思えませんが念のため。というか、C99も正当なCです)。余談ですが、C99に対応したコンパイラでも、デフォルトの動作がANSI C89相当への準拠になっていたりして、コンパイルオプションを明示的に指定しないとC99で追加された機能の部分でコンパイルエラーにされたりすることがわりとあったりします。が、このスタイルのコメントについては特に警告すら出さなかったりします(オプションを指定して警告を強制することもできたりもします)。まぁ世の中そんなものです。
閑話休題。とりあえず、実行結果を見てみましょう。
0, 22: http://www.example.com 1, 7: ttp:// 5, 7: // 7, 22: www.example.com -1, -1: 3, 25: http://www.google.com/ 4, 10: ttp:// 8, 10: // 10, 25: www.google.com/ -1, -1:
regexec()の4番目の実引数として、regmatch_t型のオブジェクトの配列の先頭へのポインタを渡しています。regexecの呼び出し後、配列の先頭の要素(のメンバ)は、正規表現パターン全体にマッチした文字のオフセット位置を示す値となります。
regexec()の3番目の実引数として渡した値が2以上の場合には、4番目の実引数として同時に渡したポインタを進めた位置にあるオブジェクトにも値が格納されます。つまり、その場合には、4番目の実引数として渡すポインタは、配列の先頭要素を指しているもので、かつその配列は3番目の実引数の値以上のサイズを持つものでなければなりません。
regexec()の3番目の実引数がNだったとすると、配列の先頭からN番目の要素(のメンバ)のすべてに値が格納されます。数は0から数え始めるものだと固く信じて疑わない人がいるかもしれないので念のため断っておきますが、ここでは先頭の要素を1番目と数えていることに注意してください。位置情報として示すべき値がない場合にも、使用されなかったことを示すため、-1が格納されます。
配列の先頭の要素(のメンバ)に格納される値については既に述べましたが、2番目以降の要素(のメンバ)に格納される値は、正規表現パターン文字列の一部を"()"で囲むことで作られた正規表現にマッチした位置のオフセットです。
文字列がパターンを含んでいるかどうかさえわかれば良いような場合は、regcomp()による正規表現パターンのコンパイル時にREG_NOSUBコンパイルフラグを指定しておけば、regmatch_t型のオブジェクトを作る必要がなくなるので、ほんのわずかですがコードを簡略化できます。
#include <stdio.h>
#include <regex.h>
int main(void)
{
char *s[] = { "abcdefgeh", "ijklmnopq", };
int s_size = sizeof s / sizeof s[0];
int i;
char *pattern = "def";
regex_t reg;
regcomp(®, pattern, REG_EXTENDED|REG_NOSUB);
for(i = 0; i < s_size; i++, puts("")) {
printf("検索対象文字列: %s, パターン: %s, 結果: ", s[i], pattern);
if(regexec(®, s[i], 0, NULL, 0) != REG_NOMATCH) printf("Matched!");
else printf("No Match!");
}
return 0;
}
この例では、regexec()の3番目と4番目の引数が意味を持たなくなります。文字列がパターンを含むかどうかは、regexec()の返却値によって知ることができます。
検索対象文字列: abcdefgeh, パターン: def, 結果: Matched! 検索対象文字列: ijklmnopq, パターン: def, 結果: No Match!
大文字と小文字を区別せずにパターンマッチングを行いたい場合には、regcomp()による正規表現パターンのコンパイル時にREG_ICASEコンパイルフラグを指定します。サンプルを追加予定です。
勘違いしやすい部分ですが、regex.hのデフォルトの動作では、行頭にマッチするオペレータ'^'は文字列の先頭の空文字列にマッチし、行末にマッチするオペレータ'$'は文字列の末尾の空文字列にマッチします。文字列中に改行文字が存在したとしても、その文字はマッチングには一切関係しません。
ただし、改行文字に関する動作は、regcomp()の呼び出し時のREG_NEWLINEフラグによって変更することができます。
ところで、ここでの空文字列という用語の意味ですが、「長さ0の文字列」とは明確に違う意味で使っています。「長さ0の文字列」とは、先頭の文字が終端文字になっている文字列で、Cの仕様から見て普通の文字列です。対して「空文字列」とは本当に空の文字列を表す概念で、いかなる文字も含んでいない文字列の意味で使っています。
まず、REG_NOSUBオプションの説明で使用したコードを少し変更し、デフォルトでは改行文字には(行頭や行末のマッチングに関しては)意味がないことを確かめてみましょう。
#include <stdio.h>
#include <regex.h>
int main(void)
{
char *s[] = { "opq\nabcdefgeh", "ijklmnopq", "ijklmnopq\n", };
int s_size = sizeof s / sizeof s[0];
int i;
char *pattern = "opq$";
regex_t reg;
regcomp(®, pattern, REG_EXTENDED|REG_NOSUB);
for(i = 0; i < s_size; i++, puts("")) {
printf("検索対象文字列: %s, パターン: %s, 結果: ", s[i], pattern);
if(regexec(®, s[i], 0, NULL, 0) != REG_NOMATCH) printf("Matched!");
else printf("No Match!");
}
return 0;
}
実行結果は次のようになります。検索対象文字列が改行文字を含んでいるため出力の際に改行されてしまいますが、そのまま掲載しています。改行文字の前後が行末や行頭として扱われるのではなく、ただの文字として扱われていることがわかると思います。
検索対象文字列: opq abcdefgeh, パターン: opq$, 結果: No Match! 検索対象文字列: ijklmnopq, パターン: opq$, 結果: Matched! 検索対象文字列: ijklmnopq , パターン: opq$, 結果: No Match!
Manpage of REGEXのREG_NEWLINEコンパイルフラグの説明はこうなっています。
- 全ての文字にマッチするオペレータに改行をマッチさせない。
- 改行を含まない非マッチング文字リスト ([^...]) に改行をマッチさせない。
- regexec() の実行時に指定するフラグ eflags に REG_NOTBOL を含むかどうかにかかわらず、行頭にマッチするオペレータ (^) を改行直後の空文字列にマッチさせる。
- eflags に REG_NOTEOL を含むかどうかにかかわらず、行末にマッチするオペレータ ($) を改行直前の空文字列にマッチさせる。
後半のふたつは少しわかりにくいのですが、「空文字列」の意味を理解していればだいたいの意味がつかめると思います。実際のコードと実行結果を見れば、さらにその意味は明確になります。
regcomp()のコンパイルフラグに、
regcomp(®, pattern, REG_EXTENDED|REG_NOSUB|REG_NEWLINE);
のようにREG_NEWLINEを追加すると、実行結果は次のようになります。
検索対象文字列: opq abcdefgeh, パターン: opq$, 結果: Matched! 検索対象文字列: ijklmnopq, パターン: opq$, 結果: Matched! 検索対象文字列: ijklmnopq , パターン: opq$, 結果: Matched!
さらに、regexec()のフラグに、
if(regexec(®, s[i], 0, NULL, REG_NOTEOL) != REG_NOMATCH) printf("Matched!");
のようにREG_NOTEOLを追加すると、文字列の末尾が行末として扱われなくなります。
検索対象文字列: opq abcdefgeh, パターン: opq$, 結果: Matched! 検索対象文字列: ijklmnopq, パターン: opq$, 結果: No Match! 検索対象文字列: ijklmnopq , パターン: opq$, 結果: Matched!
ひとつの文字列の中に、正規表現パターンにマッチする部分文字列が何回現れるかを数える関数の例を示します。POSIX正規表現には、複数の部分文字列がマッチできるような場合には最長のものにマッチするというルールがあるので、ひとつの長い部分文字列にマッチしないよう、コンパイル時に与える正規表現パターンを工夫する必要があるかもしれません。
#include <regex.h>
// REG_NOSUB フラグを指定せずにコンパイルされた regex パターンバッファを渡すこと
size_t regmatch_count(const char *s, const regex_t *reg)
{
size_t match_count = 0;
regmatch_t pmatch;
if(!reg) return 0;
for(/* */; regexec(reg, s, 1, &pmatch, 0) != REG_NOMATCH; s += pmatch.rm_eo)
match_count++;
return match_count;
}
検索対象の文字列の一部分として検索した文字列が見つかったら、その部分文字列を置換したくなるのが人情です。regcomp()の実行時にREG_NOSUBコンパイルオプションを指定していなければ、regexec()の実行後にregmatch_t型のオブジェクトに格納された値からマッチした部分の開始位置と終了位置がわかるので、置換も簡単にできます。
次のコードは、オフセット位置の格納されたregmatch_tオブジェクトへのポインタを受け取り、その範囲を別の文字列に置き換える例です。regmatch_t型オブジェクトの配列のいずれかの要素を指すポインタを渡せば、パターンマッチした部分文字列の全体に限らず、一部を置き換えることもできますね。
#include <stdlib.h>
#include <string.h>
#include <regex.h>
// regexec()の実行により位置情報格納済みのregmatch_t型オブジェクトを指すポインタを渡すこと
// 置換後に部分文字列が長くなる場合、whole_stringは動的に確保された領域を指している必要がある
char *regmatch_replace(char *whole_string, const regmatch_t *match, const char *replaced)
{
const size_t src_len = (size_t)(match->rm_eo - match->rm_so);
const size_t dst_len = strlen(replaced);
char *rm_start = whole_string + match->rm_so;
char *rm_end = whole_string + match->rm_eo;
if(src_len < dst_len) {
whole_string = realloc(whole_string, strlen(whole_string) + (dst_len - src_len) + 1);
if(!whole_string) return NULL;
}
memmove(rm_start + dst_len, rm_end, strlen(rm_end) + 1);
strncpy(rm_start, replaced, dst_len);
return whole_string;
}
最終更新日 2009年6月8日