前不久是我第二次使用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
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;
}