技术: C语言中正则表达式的使用

简单说一下posix环境下C语言对于regex表达式的支持, 并给出了相关的案例.


引子

C语言中使用正则表达式可以很方便强大的来验证ip, url, email, phone等等的正确性.

gnu提供了一系列(posix compatibile)函数来支持正则表达式, 具体可以查看一下man 3 regex.
(本文在man手册的基础上, 做了相关的验证和实验, Ubuntu16 x64)

1
2
3
4
5
6
7
8
9
10
11
12
#include <sys/types.h>
#include <regex.h>

int regcomp(regex_t *preg, const char *regex, int cflags);

int regexec(const regex_t *preg, const char *string,
size_t nmatch, regmatch_t pmatch[], int eflags);

void regfree(regex_t *preg)

size_t regerror(int errcode, const regex_t *preg, char *errbuf,
size_t errbuf_size);

上述函数, 也可以用于C++代码中, 具体可以参考源码/usr/include/regex.hextern C {部分


正文

详细介绍

上面已经看到了, posix c提供了三个函数处理函数和一个错误处理函数:(reg作为其前缀)

1. regcomp()   把你写的原始pattern编译成c语言内部可以识别的格式
2. regexec()   进行正则匹配 
3. regfree()   释放存储编译后正则表达式的结构体regex_t
4. regerror()  出错处理

使用的时候, 必须先编译, 之后在进行匹配, 中间穿插着错误处理的检查, 相关工作做完之后, 进行释放.

函数的详细解释如下:

编译函数regcomp

int regcomp (regex_t *compiled, const char *pattern, int cflags)

(执行成功返回0)

regex_t 是一个结构体数据类型(从使用的角度来说, 你不必太关心它的成员), 用来存放编译后的正则表达式;

之所以要把你写的patten字符串const char *pattern编译成结构体格式regex_t *compiled的变量, 一方面是为了进行格式调整, 就像所有的入参都要进行检查一样, 把用户传入的数据, 进行格式化调整, 使匹配更加方便, 另一方面可能是受限于所用的regex引擎, 让它可以识别和解析.

其中还提供了int cflags标志进行功能的限定和增强, 其作用在编译正则表达式期间, 其取值有:

1. REG_EXTENDED  以功能更加强大的扩展正则表达式的方式进行匹配
2. REG_ICASE   匹配字母时忽略大小写
3. REG_NOSUB  不用存储匹配后的结果,只返回是否成功匹配
4. REG_NEWLINE

(可以混合使用)
(看它的源码发现, 不设置参数直接传参数0就可以了; REG_EXTENDED是1, 后面依次左移1位)

说明:

1. 第3个参数,如果设置该标志位, 那么在 regexec 将忽略nmatch(表示数组大小)和pmatch(数组本身)两个参数
   (简单说不需要记录匹配结果了)
2. 第4个参数,REG_NEWLINE 识别换行符, 这样'$'就可以从行尾开始匹配,'^'就可以从行的开头开始匹配
   (如果没有指定, 那么讲忽略目标串中的换行, 真个当一个字符串处理).

匹配函数regexec

int regexec (regex_t *compiled, char *string, size_t nmatch, regmatch_t matchptr[], int eflags)
  • regexec函数用来匹配我们的目标文本串const char *string. 函数执行成功返回0.
  • compiled 是已经用regcomp()编译好的正则表达式,
  • string 是目标文本串
  • size_t nmatch表示匹配结果数组的元素个数,即数组的大小
  • regmatch_t pmatch[]用来存储匹配结果的数组

regmatch_t结构体数据类型, 在 regex.h 中定义如下:

1
2
3
4
typedef struct {
regoff_t rm_so; //存放匹配文本串在目标串中的开始位置
regoff_t rm_eo; //存放结束位置
} regmatch_t;

一般是pmatch[0]存放主正则表达式, 后面的位置存放子正则表达式.

eflags 有两个值:

  • REG_NOTBOL 让特殊字符^无作用
  • REG_NOTEOL 让特殊字符$无作用

释放函数regfree

void regfree (regex_t *compiled)

当我们使用完编译好的正则表达式后或者要重新编译其他正则表达式的时候, 我们可以用这个函数清空regex_t结构体的内容.
(即必须释放在进行下一次compile)

错误处理函数regerror

size_t regerror (int errcode, regex_t *compiled, char *buffer, size_t length)

把regecomp()或者regexec()产生的错误code, 转换成包含错误信息的字符串.

当执行regcomp或者regexec产生错误的时候, 调用这个函数可以返回一个包含错误信息的字符串:

  1. errcode //regcomp或者regexec的返回值
  2. compiled //regcomp函数编译好的正则表达式, 这个值可以为NULL
  3. buffer 指向用来存放错误信息的字符串的内存空间
  4. length 指明buffer的长度 (buffer可能存不下)

size_t 返回完整的错误信息字符串的长度, 第3个参数,如果错误信息的长度大于这个length,则regerror函数会自动截断超出的字符串, 但它仍然会全部长度.所以必须比较buffer的大小和实际返回的大小.

例如:

1
2
3
4
5
6
//获取实际返回的长度
size_t real_len = regerror(errcode, compiled, buffer, sizeof(buffer));

//比较一下实际返回长度和实际设置的buffer长度, 在buffer有限长度内进行截断
buffer_len = real_len < sizeof(buffer) ? real_len : sizeof(buffer) - 1;
buffer[buffer_len] = '\0';

当然, 你或许还有更好的做法, 总之一定要注意这个可能被截断的buffer的长度问题.

代码说明

话不多说, 直接上代码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <stdio.h>
#include <regex.h>
#include <sys/types.h>


/*本程序是输出匹配或者没有匹配, 并不关心匹配结果*/
int main(int argc, char *argv[])
{
/*没有编译的原来的正则表达式*/
const char *regex_ptr = NULL;
/*需要被检查的字符串*/
const char *text_ptr = NULL;

/*编译过后的正则表达式*/
regex_t compiled_regex;
/*错误码*/
int error_code = 0;
/*存储错误信息的buffer*/
char error_msg[1024] = {0};
/*出错信息的字符串, 先初始化为0*/
size_t len_error_msg = 0; //watch out init with zero

/*运行的时候, 需要用户从命令行传入参数*/
if (argc != 3) {
printf("Usage: <%s> <RegexString> <Text>\n", argv[0]);
return -1;
}


regex_ptr = argv[1];
text_ptr = argv[2];

//do not print the result, we just care match or not.
error_code = regcomp(&compiled_regex, regex_ptr, REG_EXTENDED|REG_NOSUB);

if (error_code == 0) {
error_code = regexec(&compiled_regex, text_ptr, 0, NULL, 0);
if (error_code == 0) {
printf("%s matches %s\n", text_ptr, regex_ptr);
regfree(&compiled_regex);
return 0;
}
}
len_error_msg
= regerror(error_code, &compiled_regex, error_msg, sizeof(error_msg));

/*取短进行截断*/
len_error_msg
= len_error_msg < sizeof(error_msg) ? len_error_msg : sizeof(error_msg) - 1;
error_msg[len_error_msg] = '\0'; //watch out here, we using len_error_msg

printf("code %d, err msg: %s\n", error_code, error_msg);

/*释放结构体*/
regfree(&compiled_regex);

return 0;
}

编译 gcc -g -Wall main.c -o main
运行一下:

  1. 匹配网址(url):
    ./main "http:\/\/www\.\w+\.com\/?" "http://www.baidu.com/"

  2. 匹配邮箱(email):
    注意这种类型: hermione+regexone@hogwarts.com
    merlin.yu —> \w+([+.-]\w+)*
    merlin.yu@ —> \w+([+.-]\w+)*@
    merlin.yu@xxx.xxx —> \w+([+.-]\w+)*@\w+([+.-]\w+)*\.\w+([+.-]\w+)*

    如果’@’后面部分不允许’+’的话,可以改成:
    \w+([+.-]\w)*@\w+([.-]\w)*\.\w+([.-]\w)*

    运行
    ./main "\w+([+.-]\w)*@\w+([.-]\w)*\.\w+([.-]\w)*" "wizardmerlin945@gmail.com"


尾巴

3个步骤, regcmp(), regexec(), regfree.
中途在regexec()运行之后,一般就要开始检查是否出错.

之后有时间请把相关函数的源码读一下, 大致位置是:
`/usr/include/regex.h

如果是c++, 个人主推boost库提供的regex, 请看我的另外一篇《using-regex-in-cpp》

文章目录
  1. 1. 引子
  2. 2. 正文
    1. 2.1. 详细介绍
      1. 2.1.1. 编译函数regcomp
      2. 2.1.2. 匹配函数regexec
      3. 2.1.3. 释放函数regfree
      4. 2.1.4. 错误处理函数regerror
    2. 2.2. 代码说明
  3. 3. 尾巴
|