首页 > linux/unix > 开源正则库及其使用
2017
02-10

开源正则库及其使用

说起正则表达式(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 规则表达式入门”这篇文章里摘取出来的,是否为原始出处就不得而知了)。

  1. #include <stdio.h>  
  2. #include <string.h>  
  3. #include <regex.h>  
  4.   
  5. #define SUBSLEN 10              /* 匹配子串的数量 */  
  6. #define EBUFLEN 128             /* 错误消息buffer长度 */  
  7. #define BUFLEN 1024             /* 匹配到的字符串buffer长度 */  
  8.   
  9. int main()  
  10. {  
  11.     size_t      len;  
  12.     regex_t     re; /* 存储编译好的正则表达式,正则表达式在使用之前要经过编译 */  
  13.     regmatch_t  subs[SUBSLEN];     /* 存储匹配到的字符串位置 */  
  14.     char        matched[BUFLEN];     /* 存储匹配到的字符串 */  
  15.     char        errbuf[EBUFLEN];    /* 存储错误消息 */  
  16.     int         err, i;  
  17.   
  18.     char        src[] = "111 <title>Hello World</title> 222";    /* 源字符串 */  
  19.     char        pattern[] = "<title>(.*)</title>";    /* pattern字符串 */  
  20.   
  21.     printf("String : %s\n", src);  
  22.     printf("Pattern: \"%s\"\n", pattern);  
  23.   
  24.     /* 编译正则表达式 */  
  25.     err = regcomp(&re, pattern, REG_EXTENDED);  
  26.   
  27.     if (err) {  
  28.         len = regerror(err, &re, errbuf, sizeof(errbuf));  
  29.         printf("error: regcomp: %s\n", errbuf);  
  30.         return 1;  
  31.     }  
  32.     printf("Total has subexpression: %d\n", re.re_nsub);  
  33.     /* 执行模式匹配 */  
  34.     err = regexec(&re, src, (size_t) SUBSLEN, subs, 0);  
  35.   
  36.     if (err == REG_NOMATCH) { /* 没有匹配成功 */  
  37.          printf("Sorry, no match …\n");  
  38.          regfree(&re);  
  39.          return 0;  
  40.     } else if (err) {  /* 其它错误 */  
  41.         len = regerror(err, &re, errbuf, sizeof(errbuf));  
  42.         printf("error: regexec: %s\n", errbuf);  
  43.         return 1;  
  44.     }  
  45.   
  46.     /* 如果不是REG_NOMATCH并且没有其它错误,则模式匹配上 */  
  47.     printf("\nOK, has matched …\n\n");  
  48.     for (i = 0; i <= re.re_nsub; i++) {  
  49.          len = subs[i].rm_eo – subs[i].rm_so;  
  50.          if (i == 0) {  
  51.              printf ("begin: %d, len = %d  ", subs[i].rm_so, len); /* 注释1 */  
  52.          } else {  
  53.              printf("subexpression %d begin: %d, len = %d  ", i, subs[i].rm_so, len);   
  54.          }  
  55.          memcpy (matched, src + subs[i].rm_so, len);  
  56.          matched[len] = '\0';  
  57.          printf("match: %s\n", matched);  
  58.     }  
  59.   
  60.     regfree(&re);   /* 用完了别忘了释放 */  
  61.     return (0);  
  62. }  

 

执行结果是:

  1. String : 111 <title>Hello World</title> 222  
  2. Pattern: "<title>(.*)</title>"  
  3. Total has subexpression: 1  
  4.   
  5. OK, has matched …  
  6.   
  7. begin: 4, len = 26  match: <title>Hello World</title>  
  8. subexpression 1 begin: 11, len = 11  match: Hello World  

 

从示例程序可以看出,使用之前先用regcomp()编译一下,然后调用regexec()进行实际匹配。如果只是看有没有匹配成功,掌握这2个函数的用法即可。有时候我们想要取得匹配后的子表达式,比如示例中想获得title是什么,需要用小括号 "( )"把子表达式括起来"<title>(.*)</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  (http://www.pcre.org
PCRE的名字就说明了是Perl Compatible,熟悉Perl、PHP的人使用起来完全没有问题。PCRE有非常丰富的使用说明和示例代码(看看pcredemo.c就能明白基本的用法),下面的程序只是把上面regex改为pcre。

  1. /* Compile thuswise:     
  2. *   gcc -Wall pcre1.c -I/usr/local/include -L/usr/local/lib -R/usr/local/lib -lpcre 
  3. *       
  4. */       
  5.   
  6. #include <stdio.h>  
  7. #include <string.h>  
  8. #include <pcre.h>   
  9.                   
  10. #define OVECCOUNT 30    /* should be a multiple of 3 */  
  11. #define EBUFLEN 128              
  12. #define BUFLEN 1024             
  13.           
  14. int main()   
  15. {                 
  16.         pcre            *re;   
  17.         const char      *error;  
  18.         int             erroffset;  
  19.         int             ovector[OVECCOUNT];  
  20.         int             rc, i;  
  21.           
  22.         char            src    [] = "111 <title>Hello World</title> 222";  
  23.         char            pattern   [] = "<title>(.*)</title>";  
  24.                   
  25.         printf("String : %s/n", src);  
  26.         printf("Pattern: /"%s/"/n", pattern);  
  27.           
  28.   
  29.         re = pcre_compile(pattern, 0, &error, &erroffset, NULL);  
  30.         if (re == NULL) {  
  31.                 printf("PCRE compilation failed at offset %d: %s/n", erroffset, error);  
  32.                 return 1;  
  33.         }  
  34.   
  35.         rc = pcre_exec(re, NULL, src, strlen(src), 0, 0, ovector, OVECCOUNT);  
  36.         if (rc < 0) {  
  37.                 if (rc == PCRE_ERROR_NOMATCH) printf("Sorry, no match …/n");  
  38.                 else    printf("Matching error %d/n", rc);  
  39.                 free(re);  
  40.                 return 1;  
  41.         }  
  42.   
  43.         printf("/nOK, has matched …/n/n");  
  44.   
  45.         for (i = 0; i < rc; i++) {  
  46.                 char *substring_start = src + ovector[2*i];  
  47.                 int substring_length = ovector[2*i+1] – ovector[2*i];  
  48.                 printf("%2d: %.*s/n", i, substring_length, substring_start);  
  49.         }  
  50.   
  51.         free(re);  
  52.         return 0;  
  53. }  

执行结果

  1. String : 111 <title>Hello World</title> 222  
  2. Pattern: "<title>(.*)</title>"  
  3.   
  4. OK, has matched …  
  5.   
  6. 0: <title>Hello World</title>  
  7. 1: Hello World   

 

比较这2个例子可以看出,在regex用的是regcomp()、regexec(),pcre则使用pcre_compile()、pcre_exec(),用法几乎完全一致。

pcre_compile()有很多选项,详细说明参见http://www.pcre.org/pcre.txt。如果是多行文本,可以设置PCRE_DOTALL的选项pcre_complie(re, PCRE_DOTALL,….),表示'.'也匹配回车换行"/r/n"。

3、pcre++
pcre++(http://www.daemon.de/PCRE)对pcre做了c++封装,使用起来更加方便。

  1. /* 
  2. * g++ pcre2.cpp -I/usr/local/include -L/usr/local/lib -R/usr/local/lib -lpcre++ -lpcre 
  3. */  
  4. #include <string>  
  5. #include <iostream>  
  6. #include <pcre++.h>  
  7.   
  8. using namespace std;  
  9. using namespace pcrepp;  
  10.   
  11. int main()  
  12. {  
  13.         string src("111 <title>Hello World</title> 222");  
  14.         string pattern("<title>(.*)</title>");  
  15.   
  16.         cout << "String : " << src << endl;  
  17.         cout << "Pattern : " << pattern << endl;  
  18.   
  19.         Pcre reg(pattern, PCRE_DOTALL);  
  20.         if (reg.search(src) == true) { //  
  21.                 cout << "\nOK, has matched …\n\n";  
  22.                 for(int pos = 0; pos < reg.matches(); pos++) {  
  23.                         cout << pos << ": " << reg[pos] << endl;  
  24.                 }  
  25.         } else {  
  26.                 cout << "Sorry, no match …\n";  
  27.                 return 1;  
  28.         }  
  29.   
  30.         return 0;  
  31. }  

 

执行结果

  1. String : 111 <title>Hello World</title> 222  
  2. Pattern : <title>(.*)</title>  
  3.   
  4. OK, has matched …  
  5.   
  6. 0: Hello World  

 

4、oniguruma
还有一个正则表达式的库oniguruma(http://www.geocities.jp/kosako3/oniguruma/),对于东亚文字支持比较好,开始是用在ruby上,也可用于C++,是日本的开发人员编写的。大多数人都不会用到,也就不做介绍了。如果有疑问可以通过email来讨论它的用法。

5、DEELX

DEELX (http://www.regexlab.com/zh/deelx/)是一个在 C++ 环境下的与 Perl 兼容的正则表达式引擎。是 RegExLab 开展的一个研究开发项目。所有代码都在一个.h文件中。

  1. #include "deelx.h"  
  2. #include <stdio.h>  
  3.   
  4. int find_remark(const char * string, int & start, int & end)  
  5. {  
  6.     // declare  
  7.     static CRegexpT <char> regexp("///*((?!//*/).)*(//*/)?|//([^//x0A-//x0D////]|////.)*");  
  8.   
  9.     // find and match  
  10.     MatchResult result = regexp.Match(string);  
  11.   
  12.     // result  
  13.     if( result.IsMatched() )  
  14.     {  
  15.         start = result.GetStart();  
  16.         end   = result.GetEnd  ();  
  17.         return 1;  
  18.     }  
  19.     else  
  20.     {  
  21.         return 0;  
  22.     }  
  23. }  
  24.   
  25. int main(int argc, char * argv[])  
  26. {  
  27.     char * code1 = "int a; /* a */";  
  28.     char * code2 = "int a;";  
  29.   
  30.     int start, end;  
  31.   
  32.     if( find_remark(code1, start, end) )  
  33.         printf("In code1, found: %.*s\n", end – start, code1 + start);  
  34.     else  
  35.         printf("In code1, not found.\n");  
  36.   
  37.     if( find_remark(code2, start, end) )  
  38.         printf("In code2, found: %.*s\n", end – start, code2 + start);  
  39.     else  
  40.         printf("In code2, not found.\n");  
  41.   
  42.     return 0;  
  43. }  

执行结果

  1. In code1, found: /* a */  
  2. In code2, not found.  

 

 

 

6、Regular Expression的内部实现
关于Regular Expression的实现,用到了不少自动机理论(Automata Theory)的知识,有兴趣的可以找这方面的资料来看,这本书“ Introduction to Automata Theory, Languages, and Computation”写的很好,编译原理的书也有这方面的内容。

最后编辑:
作者:liujg
真实-不弄虚,不做假,做自己,不违心; 踏实-不浮躁,不盲从,不急功,不近利; 实学-不投机,不取巧,勤于学,精于业。