说起正则表达式(Regular Expression),也许有的朋友天天都在使用,比如grep、vim、sed、awk,只是可能对这个名词不大熟悉。正则表达式一般简写为regex或者regexp,甚至是RE。关于正则表达式的介绍,有很多的文章,用搜索引擎查找就可以找到很不错的使用说明。但是在C/C++语言中如何去使用,相应的介绍比较缺乏。大多数C标准库自带regex,可以通过/usr/include/regex.h去看,或者man regex看使用说明。perl,php等语言更是提供了功能强大的正则表达式,最著名的C语言正则表达式库为PCRE(Perl Compatible Regular Expression)。本文主要对regex和pcre的使用做一点入门介绍。 1、regex
regex的使用非常简单,只要看一下示例代码1就能明白(示例代码是从“GNU C 规则表达式入门”这篇文章里摘取出来的,是否为原始出处就 不得而知了)。 CODE:#include #include #include #define SUBSLEN 10 /* 匹配子串的数量 */ #define EBUFLEN 128 /* 错误消息buffer长度 */ #define BUFLEN 1024 /* 匹配到的字符串buffer长度 */ int main() { size_t len; regex_t re; /* 存储编译好的正则表达式,正则表达式在使用之前要经过编译 */ regmatch_t subs [SUBSLEN]; /* 存储匹配到的字符串位置 */ char matched [BUFLEN]; /* 存储匹配到的字符串 */ char errbuf [EBUFLEN]; /* 存储错误消息 */ int err, i; char src [] = "111 Hello World 222"; /* 源字符串 */ char pattern [] = "(.*)"; /* pattern字符串 */ printf("String : %s\n", src); printf("Pattern: \"%s\"\n", pattern); /* 编译正则表达式 */ err = regcomp(&re, pattern, REG_EXTENDED); if (err) { len = regerror(err, &re, errbuf, sizeof(errbuf)); printf("error: regcomp: %s\n", errbuf); return 1; } printf("Total has subexpression: %d\n", re.re_nsub); /* 执行模式匹配 */ err = regexec(&re, src, (size_t) SUBSLEN, subs, 0); if (err == REG_NOMATCH) { /* 没有匹配成功 */ printf("Sorry, no match ...\n"); regfree(&re); return 0; } else if (err) { /* 其它错误 */ len = regerror(err, &re, errbuf, sizeof(errbuf)); printf("error: regexec: %s\n", errbuf); return 1; } /* 如果不是REG_NOMATCH并且没有其它错误,则模式匹配上 */ printf("\nOK, has matched ...\n\n"); for (i = 0; i <= re.re_nsub; i++) { len = subs[i].rm_eo - subs[i].rm_so; if (i == 0) { printf ("begin: %d, len = %d ", subs[i].rm_so, len); /* 注释1 */ } else { printf("subexpression %d begin: %d, len = %d ", i, subs[i].rm_so, len); } memcpy (matched, src + subs[i].rm_so, len); matched[len] = '\0'; printf("match: %s\n", matched); } regfree(&re); /* 用完了别忘了释放 */ return (0); } 执行结果是 CODE:String : 111 Hello World 222 Pattern: "(.*)" Total has subexpression: 1 OK, has matched ... begin: %, len = 4 match: Hello World subexpression 1 begin: 11, len = 11 match: Hello World 从示例程序可以看出,使用之前先用regcomp()编译一下,然后调用regexec()进行实际匹配。如果只是看有没有匹配成功,掌握这2个函数的用法即可。有时候我们想要取得匹配后的子表达式,比如示例中想获得title是什么,需要用小括号 "( )"把子表达式括起来"(.*)",表达式引擎会将小括号 "( )" 包含的表达式所匹配到的字符串记录下来。在获取匹配结果的时候,小括号包含的表达式所匹配到 的字符串可以单独获取,示例程序就是我用来获取http网页的主题(title)的方式。 regmatch_t subs[SUBSLEN]是用来存放匹配位置的,subs[0]里存放这个匹配的字符串位置,subs[1]里存放第一个子表达式的匹配位置,也就是例子中的title,通过结构里的rm_so和rm_eo可以取到,这一点很多人不太注意,应该强调一下。 注释1:开始调试代码的时候是在FreeBSD 6.2上进行的,print出来的len总是0,但print出来的字符串又没错,很是迷惑,把它放到Linux上则完全正常,后来仔细检查才发现rm_so在Linux上是32位,在FreeBSD上是64位,用%d的话实际取的是rm_so的高32位,而不是实际的len,把print rm_so的地方改为%llu就可以了。 regex虽然简单易用,但对正则表达式的支持不够强大,中文处理也有问题,于是引出了下面要说的PCRE。
2、PCRE ()
PCRE的名字就说明了是Perl Compatible,熟悉Perl、PHP的人使用起来完全没有问题。PCRE有非常丰富的使用说明和示例代码(看看 pcredemo.c就能明白基本的用法),下面的程序只是把上面regex改为pcre。 CODE:/* Compile thuswise: * gcc -Wall pcre1.c -I/usr/local/include -L/usr/local/lib -R/usr/local/lib -lpcre * */ #include #include #include #define OVECCOUNT 30 /* should be a multiple of 3 */ #define EBUFLEN 128 #define BUFLEN 1024 int main() { pcre *re; const char *error; int erroffset; int ovector[OVECCOUNT]; int rc, i; char src [] = "111 Hello World 222"; char pattern [] = "(.*)"; printf("String : %s\n", src); printf("Pattern: \"%s\"\n", pattern); re = pcre_compile(pattern, 0, &error, &erroffset, NULL); if (re == NULL) { printf("PCRE compilation failed at offset %d: %s\n", erroffset, error); return 1; } rc = pcre_exec(re, NULL, src, strlen(src), 0, 0, ovector, OVECCOUNT); if (rc < 0) { if (rc == PCRE_ERROR_NOMATCH) printf("Sorry, no match ...\n"); else printf("Matching error %d\n", rc); free(re); return 1; } printf("\nOK, has matched ...\n\n"); for (i = 0; i < rc; i++) { char *substring_start = src + ovector[2*i]; int substring_length = ovector[2*i+1] - ovector[2*i]; printf("%2d: %.*s\n", i, substring_length, substring_start); } free(re); return 0; } 执行结果是: CODE:String : 111 Hello World 222 Pattern: "(.*)" OK, has matched ... 0: Hello World 1: Hello World 比较这2个例子可以看出,在regex用的是regcomp()、regexec(),pcre则使用pcre_compile()、pcre_exec(),用法几乎完全一致。 pcre_compile()有很多选项,详细说明参见。如果是多行文本,可以设置PCRE_DOTALL的选项pcre_complie(re, PCRE_DOTALL,....),表示'.'也匹配回车换行"\r\n"。 3、pcre++
pcre++()对pcre做了c++封装,使用起来更加方便。 CODE:/* * g++ pcre2.cpp -I/usr/local/include -L/usr/local/lib -R/usr/local/lib -lpcre++ -lpcre */ #include #include #include using namespace std; using namespace pcrepp; int main() { string src("111 Hello World 222"); string pattern("(.*)"); cout << "String : " << src << endl; cout << "Pattern : " << pattern << endl; Pcre reg(pattern, PCRE_DOTALL); if (reg.search(src) == true) { // cout << "\nOK, has matched ...\n\n"; for(int pos = 0; pos < reg.matches(); pos++) { cout << pos << ": " << reg[pos] << endl; } } else { cout << "Sorry, no match ...\n"; return 1; } return 0; } 执行结果是: CODE:String : 111 Hello World 222 Pattern : (.*) OK, has matched ... 0: Hello World 4、oniguruma
还有一个正则表达式的库oniguruma(),对于东亚文字支持比较好,开始是用在ruby上,也可用于C++,是日本的开发人员编写的。大多数人都不会用到,也就不做介绍了。如果有疑问可以通过email来讨论它的用法。
5、Regular Expression的内部实现
关于Regular Expression的实现,用到了不少自动机理论(Automata Theory)的知识,有兴趣的可以找这方面的资料来看,这本书“ Introduction to Automata Theory, Languages, and Computation”写的很好,编译原理的书也有这方面的内容。
|
|