使用GNU C正则表达式

本文介绍了如何在C语言中使用GNU正则表达式库,包括从理解regex.h头文件开始,定义模板字符串,设定语法风格,编译模板,到执行匹配的完整流程。旨在提供一个清晰的C语言正则表达式使用教程,避免再次遗忘或重复学习。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值