C:基于GNU regex(regex.h)regexec实现正则表达式多次匹配
Posted 10km
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C:基于GNU regex(regex.h)regexec实现正则表达式多次匹配相关的知识,希望对你有一定的参考价值。
GNU regex是GNU提供的跨平台的POSIX 正则表达式库(C语言)。
不算GNU提供的扩展函数,POSIX标准的regex库总共就4个函数regcomp,regerror,regexec,regfree
.
我们知道 regexec
不能通过一次调用找到字符串中所有满足匹配条件的字符串位置,所以需要通过步进偏移的方式循环执行regexec
才能把字符串中所有满足条件的匹配找出来, 每一次匹配的起始偏移是上一次匹配到的字符串结束偏移。
在上一篇博客《C: GNU regex library (regex.h)正则表达式调用示例》中,我已经 实现了正则表达式匹配多个捕获组(catch group),并且循环执行regexec
实现多次实现。本文就是对上一次的实现进行改进,将循环匹配逻辑进一步封装成易用的函数rx_search
.
做这个封装对于我的现实意义是,最近工作的一个项目运行在嵌入式平台上,设备提供的SDK中有GNU regex库,但是是非常老的版本,只有4个函数regcomp,regerror,regexec,regfree
.没有提供高版本才有的re_search
函数。所以如果想实现多次匹配,只能自己实现了。
以下是rx_search
的实现代码:
rx_serach
//************************************
// 用指定的正则表达式在字符串中查找所有匹配
// @param const char * input 待匹配的字符串
// @param const char * pattern 正则表达式
// @param size_t groupcnt 正则表达式中捕获组数量(包含默认组group 0),为0时使用默认值,即pattern编译后regex_t的re_nsub+1
// regex_t.re_nsub字段为正则表达式中子表达式的数量,子表达式又分为捕获和非捕获两种.
// 所以re_nsub + 1肯定大于等于表达式中所有捕获组(包含默认组group 0)的数量
// @param int eflags 正则表达匹配执行标志,参见 regexec
// @param search_match_t * _psmatch [out] 保存字符串所有匹配的位置
// @return int 匹配成功返回匹配匹配的数量,没有匹配返回0,失败返回-1,
// 调用层必须调用rx_search_match_uninit释放分配的空间
//************************************
int rx_serach(const char* input, const char* pattern, size_t groupcnt, int eflags, search_match_t* _psmatch)
{
if (NULL == input || NULL == pattern || NULL == _psmatch)
{
printf("%s:%d NULL ARGUMENT\\n",__FILE__,__LINE__);
return 0;
}
regex_t reg;
/************************************************************************/
/* 编译正则表达式,编译成功的 regex_t 对象才可以被后续的 regexec 使用 */
/************************************************************************/
int c = regcomp(®, pattern, REG_EXTENDED);
if (0 != c)
{
/************************************************************************/
/* 正则表达式编译出错输出错误信息 */
/* 调用 regerror 将错误信息输出到 regerrbuf 中 */
/* regerrbuf 末尾置0,确保上面调用regerror 导致 regerrbuf 溢出的情况下, */
/* 字符串仍有有结尾0 */
/* 然后 printf 输出 */
/************************************************************************/
regerror(c, ®, regerrbuf, sizeof(regerrbuf));
regerrbuf[sizeof(regerrbuf) - 1] = '\\0';
printf("%s:%d %s\\n",__FILE__,__LINE__, regerrbuf);
return -1;
}
if (0 == groupcnt)
{
groupcnt = reg.re_nsub + 1;
}
c = rx_search_match_init(_psmatch, groupcnt);
if (0 != c)
{
/** search_match_t 初始化失败,释放前面初始化成功的 regex_t */
regfree(®);
return c;
}
/** 起始匹配的偏移量 */
size_t offset = 0;
/************************************************************************/
/* regexec 不能通过一次调用找到字符串中所有满足匹配条件的字符串位置, */
/* 所以需要通过步进偏移的方式循环查找字符串中所有匹配的字符串, */
/* 每一次匹配的起始偏移是上一次匹配到的字符串结束偏移 */
/************************************************************************/
do {
printf("MATCH start %d\\n", (int)offset);
/** 输出缓冲区扩容 */
regmatch_t* pmatch = rx_search_match_ensure(_psmatch, 1);
if (NULL == pmatch)
{
printf("%s:%d MEMORY ERROR for rx_search_match_ensure\\n",__FILE__,__LINE__);
c = -1;
break;
}
/** 正则表达式匹配的起始地址 */
const char* p = input + offset;
/************************************************************************/
/* regmatch_t 用于记录正则表达匹配的结果,每一个 regmatch_t 记录一个捕获 */
/* 组(catch group)的在字符串中的起始位置。 */
/* 如果调用 regexec 时如果不提供 regmatch_t(nmatch为0,pmatch为NULL), */
/* 或者提供的 regmatch_t 数组长小于正则表达式中全部捕获组的数量, */
/* regexec 也能正常匹配,只是无法记录匹配的位置 */
/* 或不能完全记录所有的匹配结果 */
/************************************************************************/
c = regexec(®, p, _psmatch->groupcnt, pmatch, eflags);
if (REG_NOMATCH == c)
{
/************************************************************************/
/** 没有找到匹配结束循环 */
/************************************************************************/
printf("MATCH FINISHED\\n");
break;
}
else if (0 == c)
{
/** 匹配计数加1 */
_psmatch->matchcnt++;
/** 找到匹配,则输出匹配到的所有捕获组(catch group) */
printf("%d MATCH (%d-%d)\\n", (int)_psmatch->matchcnt, pmatch[0].rm_so, pmatch[0].rm_eo);
for (int i = 0; i < _psmatch->groupcnt; ++i)
{
printf("group %d :<<", i);
print_str(p, pmatch[i].rm_so, pmatch[i].rm_eo);
printf(">>\\n");
}
/** (group 0)的结束位置 */
size_t eo = pmatch[0].rm_eo;
for (int i = 0; i < _psmatch->groupcnt; ++i)
{
/** 偏移量修改为相对于字符串起始位置 */
pmatch[i].rm_so += (int)offset;
pmatch[i].rm_eo += (int)offset;
}
/************************************************************************/
/* 使用整体匹配捕获组0(group 0)的结束位置的更新偏移量, */
/* 下一次匹配从当前匹配的结束位置开始 */
/************************************************************************/
offset += eo;
continue;
}
else
{
/************************************************************************/
/** regexec 调用出错输出错误信息,结束循环 */
/************************************************************************/
regerror(c, ®, regerrbuf, sizeof(regerrbuf));
regerrbuf[sizeof(regerrbuf) - 1] = '\\0';
printf("%s\\n", regerrbuf);
c = -1;
break;
}
} while (1);
printf("%d MATCH FOUND\\n", (int)_psmatch->matchcnt);
/************************************************************************/
/** regfree 必须与 regcomp 配对使用,否则会发生内存泄露 */
/************************************************************************/
regfree(®);
/** REG_NOMATCH 为正常循环结束标志 */
if (c != REG_NOMATCH)
{
/** 出错时释放 search_match_t 所占内存 */
rx_search_match_uninit(_psmatch);
return c;
}
return (int)_psmatch->matchcnt;
}
search_match_t
因为我们并不可能预知字符串中有多少满足正则表达式条件的匹配。所以我设计了一个search_match_t
结构用来保存匹配的结果数据,rx_search
执行结果就保存在search_match_t
中
/************************************************************************/
/* 保存执行 regexec 多次匹配数据 */
/************************************************************************/
typedef struct search_match_t
{
/** 捕获组数量(包含group 0) */
size_t groupcnt;
/** 可保存匹配次数 */
size_t capacity;
/** 匹配次数 */
size_t matchcnt;
/************************************************************************/
/* 以在字符串中的顺序保存每一次匹配的数据, */
/* 数组长度为 capacity*groupcnt, */
/* rx_search在执行时会根据需要自动对数组长度扩容 */
/************************************************************************/
regmatch_t* pmatch;
}search_match_t;
rx_search_match_ensure
因为我们并不可能预知字符串中有多少满足正则表达式条件的匹配,所以在执行rx_search
时当匹配数量超过search_match_t.pmatch
数组容量时,会根据需要自动对search_match_t.pmatch
数组长度扩容,
以下是search_match_t
扩容函数rx_search_match_ensure
的实现:
//************************************
// search_match_t 扩容,确保 search_match_t 中有足够的空闲空间保存 freecnt 指定大小的匹配数据
// 扩容部分内存清零
// @param search_match_t * _psmatch
// @param size_t freecnt 要求空闲保存的匹配空间数量(每个匹配需要的regmatch_t数量为groupcnt)
// @return regmatch_t* 扩容成功返回最后空闲的regmatch_t起始位置,否则返回NULL
//************************************
static regmatch_t* rx_search_match_ensure(search_match_t * _psmatch, size_t freecnt)
{
regmatch_t *newbuffer = NULL;
size_t newsize = 0;
size_t newcapacity = 0;
if ((_psmatch == NULL) || (_psmatch->pmatch == NULL))
{
printf("%s:%d NULL ARGUMENT\\n",__FILE__,__LINE__);
return NULL;
}
if ((_psmatch->capacity > 0) && (_psmatch->matchcnt >= _psmatch->capacity))
{
printf("%s:%d INVALID matchcnt %d\\n",__FILE__,__LINE__,(int)_psmatch->matchcnt);
return NULL;
}
if (freecnt > (INT_MAX / 64))
{
printf("%s:%d TOO LARGE ARGUMENT matchcnt %d\\n",__FILE__,__LINE__,(int)freecnt);
return NULL;
}
if (freecnt <= (_psmatch->capacity - _psmatch->matchcnt))
{
return _psmatch->pmatch + (_psmatch->matchcnt * _psmatch->groupcnt);
}
/** 以16整数倍扩容 */
newcapacity = ((freecnt + _psmatch->matchcnt + 16 - 1) >> 4 << 4);
newsize = newcapacity * _psmatch->groupcnt * sizeof(regmatch_t);
/* reallocate with realloc if available */
newbuffer = (regmatch_t*)realloc(_psmatch->pmatch, newsize);
if (newbuffer == NULL)
{
printf("%s:%d MEM ERROR\\n",__FILE__,__LINE__);
free(_psmatch->pmatch);
_psmatch->capacity = 0;
_psmatch->groupcnt = 0;
_psmatch->matchcnt = 0;
_psmatch->pmatch = NULL;
return NULL;
}
size_t oldsize = _psmatch->matchcnt * _psmatch->groupcnt * sizeof(regmatch_t);
size_t expsize = (newcapacity - _psmatch->matchcnt) * _psmatch->groupcnt * sizeof(regmatch_t);
/** 扩容部分清零 */
memset(newbuffer + oldsize, 0, expsize);
_psmatch->capacity = newcapacity;
_psmatch->pmatch = newbuffer;
printf("%s:%d pmatch buffer expand to %d match\\n",__FILE__,__LINE__, (int)newcapacity);
return _psmatch->pmatch + (_psmatch->matchcnt * _psmatch->groupcnt);
}
//************************************
// 释放search_match_t中分配的空间,
// @param search_match_t * _psmatch
//************************************
void rx_search_match_uninit(search_match_t* _psmatch)
{
if (_psmatch)
{
free(_psmatch->pmatch);
memset(_psmatch, 0, sizeof(search_match_t));
}
}
rx_search_match_uninit
rx_search
执行后,search_match_t
中分配的内存调用者要负责释放
这就需要另外一个函数rx_search_match_uninit
来完成search_match_t
的释放
//************************************
// 释放search_match_t中分配的空间,
// @param search_match_t * _psmatch
//************************************
void rx_search_match_uninit(search_match_t* _psmatch)
{
if (_psmatch)
{
free(_psmatch->pmatch);
memset(_psmatch, 0, sizeof(search_match_t));
}
}
完整代码
以下是可以用 MSVC/GCC 直接编译运行的完整代码(含测试)
rx_search_test.c
/************************************************************************/
/* rx_search_test.c */
/* GNU Regex 测试 */
/* rx_search 实现调用regexe多次搜索字符串中满足正则表达式的匹配, */
/* 并保存到 search_match_t */
/* author guyadong */
/************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <regex.h>
#include <limits.h>
#include <string.h>
/** regex 错误输出缓冲区 */
static char regerrbuf[256];
/** 输出字符串中指定范围的字符到控制台 */
void print_str(const char* input, size_t _start, size_t _end)
{
if (input)
{
for (size_t i = _start; i < _end; ++i)
{
printf("%c", input[i]);
}
}
}
/************************************************************************/
/* 保存执行 regexec 多次匹配数据 */
/************************************************************************/
typedef struct search_match_t
{
/** 捕获组数量(包含group 0) */
size_t groupcnt;
/** 可保存匹配次数 */
size_t capacity;
/** 匹配次数 */
size_t matchcnt;
/************************************************************************/
/* 以在字符串中的顺序保存每一次匹配的数据, */
/* 数组长度为 capacity*groupcnt, */
/* rx_search在执行时会根据需要自动对数组长度扩容 */
/************************************************************************/
regmatch_t* pmatch;
}search_match_t;
//************************************
// search_match_t 初始化,以初始容量16分配内存,并将内存清零
// 如果匹配次数超过容量,会调用 rx_search_match_ensure 自动扩容
// @param search_match_t * _psmatch
// @param size_t groupcnt
// @return int 成功返回0,否则返回-1
//************************************
static int rx_search_match_init(search_match_t* _psmatch,size_t groupcnt)
{
if(NULL == _psmatch){
return -1;
}
_psmatch->capacity = 16;
_psmatch->matchcnt = 0;
_psmatch->groupcnt = groupcnt;
size_t size = sizeof(regmatch_t) * groupcnt * _psmatch->capacity;
_psmatch->pmatch = (regmatch_t*)malloc(size);
if(!_psmatch->pmatch)
{
printf("%s:%d MEM ERROR\\n",__FILE__,__LINE__);
return -1;
}
/** 内存清零 */
memset(_psmatch->pmatch, 0, size);
return 0;
}
//************************************
// search_match_t 扩容,确保 search_match_t 中有足够的空闲空间保存 freecnt 指定大小的匹配数据
// 扩容部分内存清零
// @param search_match_t * _psmatch
// @param size_t freecnt 要求空闲保存的匹配空间数量(每个匹配需要的regmatch_t数量为groupcnt)
// @return regmatch_t* 扩容成功返回最后空闲的regmatch_t起始位置,否则返回NULL
//************************************
static regmatch_t* rx_search_match_ensure(search_match_t * _psmatch, size_t freecnt)
{
regmatch_t *newbuffer = NULL;
size_t newsize = 0;
size_t newcapacity = 0;
if ((_psmatch == NULL) || (_psmatch->pmatch == NULL))
{
printf("%s:%d NULL ARGUMENT\\n",__FILE__,__LINE__);
return NULL;
}
if ((_psmatch->capacity > 0) && (_psmatch->matchcnt >= _psmatch->capacity))
{
printf("%s:%d INVALID matchcnt %d\\n",__FILE__,__LINE__,(int)_psmatch->matchcnt);
return NULL;
}
if (freecnt > (INT_MAX / 64))
{
printf("%s:%d TOO LARGE ARGUMENT matchcnt %d\\n",__FILE__,__LINE__,(int)freecnt);
return NULL;
}
if (freecnt <= (_psmatch->capacity - _psmatch->matchcnt))
{
return _psmatch->pmatch + (_psmatch->matchcnt * _psmatch->groupcnt);
}
/** 以16整数倍扩容 */
newcapacity = ((freecnt + _psmatch->matchcnt + 16 - 1) >> 4 << 4);
newsize = newcapacity * _psmatch->groupcnt * sizeof(regmatch_t);
/* reallocate with realloc if available */
newbuffer = (regmatch_t*)realloc(_psmatch->pmatch, newsize);
if (newbuffer == NULL)
{
printf("%s:%d MEM ERROR\\n",__FILE__,__LINE__);
free(_psmatch->pmatch);
_psmatch->capacity = 0;
_psmatch->groupcnt = 0;
_psmatch->matchcnt = 0;
_psmatch->pmatch = NULL;
return NULL;
}
size_t oldsize = _psmatch->matchcnt * _psmatch->groupcnt * sizeof(regmatch_t);
size_t expsize = (newcapacity - _psmatch->matchcnt) * _psmatch->groupcnt * C: GNU regex library (regex.h)正则表达式调用示例