使用GNU C正则表达式

来源:互联网 发布:ife矩阵分析案例 编辑:程序博客网 时间:2024/05/21 16:01

    前不久是我第二次使用C语言的正则表达式库。用的时候竟发现以前用过一次的全忘了,没办法,只有从新去分析regex.h这个头文件的内容,猜着它是怎么工作的。

    看来很有必要记下来,免得第三次用的时候再去猜一遍。

    “regex.h”是Linux系统默认安装的一个头文件,我们就是用里面的函数来使用正则表达式的功能。


    先看看它的大体流程吧:

  • 定义模板字符串
  • 设置要使用的语法风格
  • 编译模板
  • 匹配
    定义模板这个不说了。设置语法风格,就是选择你要使用那一种正则表达式规则。各种不同的程序里面,对正则表达式的元字符以及一些规则的定义是不一样的。在这一个步骤,可以设置一种最适合,或者自己最熟悉的风格。编译模板,可能一部分原因是为了效率,这个不想深究。匹配,这个不用说了吧。
    假设你要处理的是这样一种字符串:一串11位数字的电话号码,彼此用“;”分隔,在每个号码后面可能会有“,b”表示黑名单里的“,w”表示白名单,或者没有(如:“18753432222,b;19885812345,w;18712345678;18787654321,w”)。看如下代码:
#include <stdio.h>#include <regex.h>#include <stdlib.h>#include <string.h>static int pat_compiled = 0;const static char* pat_number = "^[0-9]{11,11}(,[bw])?(;[0-9]{11,11}(,[bw])?)*$;static regex_t pat_buf[1];int print_number(char* src) {int match_ret = 0;struct re_registers match_rlt[1];char* tmp_p = NULL;int i;if (0 == pat_compiled) {const char* compile_ret = NULL;// 设置正则表达式使用的风格,它决定那些元字符具有特殊的意义。re_set_syntax(RE_SYNTAX_POSIX_EGREP);// 使用之前,先对模板进行编译if (NULL != (compile_ret = re_compile_pattern(pat_number,strlen(pat_number),pat_buf))) {fprintf(stderr,"%s\n",compile_ret);return -1;}pat_compiled = 1;}memset(match_rlt,0,sizeof(struct re_registers));match_ret = re_match(pat_buf,src,strlen(src),0,match_rlt);if (-2 == match_ret) {fprintf(stderr,"Internal error!\n");return -1;}if (-1 == match_ret) {printf("No match!\n");return 0;}for (i = 0; i < match_rlt->num_regs; ++i) {if (-1 == match_rlt->start[i]) {continue;}tmp_p = strndup(src + match_rlt->start[i],match_rlt->end[i] - match_rlt->start[i]);printf("start: %d end: %d\n",match_rlt->start[i],match_rlt->end[i]);printf("%s\n",tmp_p);free(tmp_p);}return 0;}int main(int argc, char **argv) {char* str = "18753432222,b;19885812345,w;18712345678;18787654321,w";print_number(str);return 0;}

程序输出:
start: 0 end: 53
18753432222,b;19885812345,w;18712345678;18787654321,w
start: 11 end: 13
,b
start: 39 end: 53
;18787654321,w
start: 51 end: 53
,w

re_set_syntax(RE_SYNTAX_POSIX_EGREP);
首先,程序选择的是POSIX EGREP的风格,也就是grep程序加了-E选项时候使用的风格。选这个的原因,除了熟悉意外,最重要的就是好测试表达式的正确性。

                // 使用之前,先对模板进行编译if (NULL != (compile_ret = re_compile_pattern(pat_number,strlen(pat_number),pat_buf))) {fprintf(stderr,"%s\n",compile_ret);return -1;}
编译的返回值,如果为NULL则表示成功,否则为一个指向了描述失败原因字符串的指针。

        match_ret = re_match(pat_buf,src,strlen(src),0,match_rlt);if (-2 == match_ret) {fprintf(stderr,"Internal error!\n");return -1;}if (-1 == match_ret) {printf("No match!\n");return 0;}
匹配的函数,如果返回-2表示内部错误,返回-1表示字符串不匹配,否则返回第一个匹配的字符索引。匹配结果放在match_rlt结构体中。
我们看看这个结构体的定义:
struct re_registers{  unsigned num_regs; // 放置匹配的数量  regoff_t *start;   // 数组,放置各个匹配的开始字符索引  regoff_t *end;     // 数组,防止各个匹配的结束字符索引};
这里得解释一下“匹配的数量”。匹配数不是指元字符串中匹配整个正则是模板的有几个,而是指的捕获的字串数量。
"^[0-9]{11,11}(,[bw])?(;[0-9]{11,11}(,[bw])?)*$;
三个括号,为什么会有4个捕获呢?请看第一组捕获,它把整个正则是看做一个捕获。所以一旦匹配,num_regs的值一点是括号对数加1.
那么问题又来了,源字符串是这样的:
"18753432222,b;19885812345,w;18712345678;18787654321,w"
根据
(;[0-9]{11,11}(,[bw])?)*
那么括号部分一共重复了3次,为什么才算了一次呢?
这个问题还没有想通,可能是为了用户程序更稳定的考虑。因为它根据模板中可见的括号数来捕获,使得捕获数在程序设计时就确定了,这样免去了很多可能由于处理不当可能导致的错误。不过功能上,就有所打折了。
不过仔细看re_match函数的参数设置,它给我提供了另外一个方法来实现捕获每一个号码。
/* Search in the string STRING (with length LENGTH) for the pattern   compiled into BUFFER.  Start searching at position START, for RANGE   characters.  Return the starting position of the match, -1 for no   match, or -2 for an internal error.  Also return register   information in REGS (if REGS and BUFFER->no_sub are nonzero).  */extern int re_search (struct re_pattern_buffer *__buffer, const char *__string,      int __length, int __start, int __range,      struct re_registers *__regs);/* Like `re_search', but return how many characters in STRING the regexp   in BUFFER matched, starting at position START.  */extern int re_match (struct re_pattern_buffer *__buffer, const char *__string,     int __length, int __start, struct re_registers *__regs);
通过第四个参数__start,我们可以指定匹配开始的地方。我们只用再循环中每次从匹配的下一个位置开始匹配,不就每次都取出新的号码了吗?
经测试,不管用,肯定是用的方法不对^_^,不过一件事可以通过多种不同的方法实现。
我们每次把输入的string往后推一个号码,代码如下:
#include <stdio.h>#include <regex.h>#include <stdlib.h>#include <string.h>static int pat_compiled = 0;const static char* pat_number = "^([0-9]{11,11})(,[bw])?(;[0-9]{11,11}(,[bw])?)*{1}quot;;static regex_t pat_buf[1];int print_number(char* src) {int match_ret = 0;struct re_registers match_rlt[1];char* tmp_p = NULL;int start = 0;if (0 == pat_compiled) {const char* compile_ret = NULL;// 设置正则表达式使用的风格,它决定那些元字符具有特殊的意义。re_set_syntax(RE_SYNTAX_POSIX_EGREP);// 使用之前,先对模板进行编译if (NULL != (compile_ret = re_compile_pattern(pat_number,strlen(pat_number),pat_buf))) {fprintf(stderr,"%s\n",compile_ret);return -1;}pat_compiled = 1;}while (1) {memset(match_rlt, 0, sizeof(struct re_registers));match_ret = re_match(pat_buf, src, strlen(src),0, match_rlt);if (-2 == match_ret) {fprintf(stderr, "Internal error!\n");return -1;}if (-1 == match_ret) {printf("No match!\n");return 0;}if (-1 != match_rlt->start[1]) {tmp_p = strndup(src + match_rlt->start[1],match_rlt->end[1] - match_rlt->start[1]);printf("%s %s group!\n",tmp_p,match_rlt->start[2] == -1 ? "NO" : src[match_rlt->start[2] + 1] == 'b' ? "Black" : "White");free(tmp_p);start = match_rlt->end[2] == -1 ? match_rlt->end[1] + 1 : match_rlt->end[2] + 1;src += start;}else {break;}}return 0;}int main(int argc, char **argv) {char* str = "18753432222,b;19885812345,w;18712345678;18787654321,w";print_number(str);return 0;}

原创粉丝点击