🔥博客主页: 我要成为C++领域大神
🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】
❤️感谢大家点赞👍收藏⭐评论✍️本博客致力于分享知识,欢迎大家共同学习和交流。
正则表达式(Regular Expression,简称 regex 或 regexp)是一种用来匹配字符串中字符模式的工具,经典的数据处理技术,可以实现数据查询匹配、数据清洗等等。正则表达式是一种强大的字符串处理工具,广泛应用于文本搜索、替换和处理任务。正则表达式的语法由一系列特殊字符和普通字符组成,通过这些字符的组合,可以精确地定义字符串的匹配模式。
正则表达式的实现原理
正则技术采用贪心算法完成数据模式匹配,匹配的效率更高,而且正则技术在各个系统和语言中都有实现,便于跨平台。
正则技术匹配数据的流程:
数据源(文件):
数据源可以是一个包含多行数据的文件。
每次处理一行数据,自动遍历整个文件中的所有数据。
模式匹配区域:
数据源中的每一行数据被逐行复制到模式匹配区域中。
在模式匹配区域中,正则表达式被应用于每一行数据。
grep :
grep
使用给定的正则表达式在数据源中查找匹配项。正则表达式的模式会在模式匹配区域中与每一行数据进行匹配。
正则表达式函数库:
grep
工具依赖正则表达式函数库来执行模式匹配操作。函数库提供了实现正则表达式解析和匹配的基础功能。
匹配结果处理:
如果找到匹配项,匹配的结果会被传递给后续的处理步骤。
后续步骤包括高亮显示匹配的内容或者进一步处理匹配的结果。
通过这个流程,正则表达式可以高效地在大文件中查找特定模式,并对匹配的结果进行相应的处理。
正则语句
正则语句是由若干特殊符号(表达式)构成的长字符串,这个字符串用于描述数据的规则,后续对数据进行模式匹配。
数据存储需要一定的规则和格式,便于后续数据的使用,持久化存储,不要直接无格式字节流存储
如果存储的数据格式清晰明确使用正则匹配提取数据很方便,否则正则不一定适用
正则原字符
正则符号需要参照物,单独出现无意义
\ 转义符
a* 向前一个表达式为参照,表示该表达式出现0次或多次
a+ 向前一个表达式为参照,表示该表达式出现1次或多次(+需要进行转义)
^a 向后一个表达式参照,行首字符
a$ 向前一个表达式参照,行尾字符
. 表示任意的一个字符出现1次
[abc] 表达式集合,集合出现时表示任意一个表达式
[a-z],小写字母集合 [A-Z] 大写字母集合 [0-9] 数字集合
[a-zA-Z0-9] 有效字符集合
[^] 集合取非 ,匹配取非数据
^$ 匹配空行
a(n) 以前一个表达式为参照,该表达式连续出现的频率转义
a{n} 最小连续出现n次,最大不设限制
a{n,m} 最小连续出现n次,最大连续出现m次,从m到n去匹配
(description ) 创建表达式 提升表达式颗粒度转义
| 逻辑或(descVtomcat \mysql) 字符集合中无需转义,其他位置适用需要转义
示例:匹配手机号的grep正则命令
我们需要明确手机号的规则,方便我们书写正则表达式
1.由11位数字组成
2.第二位是3-9的数字
3.后面9位是0~9的数字组成
4.第一位数字是1
grep '^1[3-9][0-9]\{9\}' phone
匹配结果:
正确筛选掉了不合法的结果
示例:匹配邮箱的grep正则命令
自定义规则:
1.用户名由8~15位数字字母组成
2.筛选出.com或.cn格式的邮箱
3.邮箱域名是有效字符组成.
grep '^[a-zA-Z0-9]\{8,15\}@[a-z0-9A-Z]\+\(\.com\|\.cn\)$' email
匹配结果:
正确筛选掉了不合法的结果
示例:使用一条命令统计日志文件中某一天的日志数量(面试常问)
如下图所示,有这么一个日志文件。现在需要我们使用命令统计6-14那天的日志数量。
我们先使用grep
命令匹配出2024-6-14那天的日志。
grep '2024-6-14.\+' time.log
但是题目要求我们使用一条统计数量,显然不能直接数,于是就需要使用管道符了。
在 Linux 命令中,|
(管道符)用于将一个命令的输出传递给另一个命令作为输入。
管道符的使用示例
将 ls
命令的输出传递给 grep
命令:
ls -l | grep ".txt"
这条命令会列出当前目录中的所有文件和目录,然后使用 grep
过滤出文件名包含 .txt
的条目。
所以我们可以将grep
命令匹配到的结果,传递给另外一个可以统计行数的命令-wc
grep '2024-6-14.\+' time.log | wc -l
成功统计出数量。
C语言中的正则表达式
正则函数
在C语言中,POSIX正则表达式库提供了一组函数,用于处理正则表达式。下面是这些函数的简要介绍:
regcomp
:编译正则表达式。regexec
:执行正则表达式匹配。regfree
:释放由regcomp
编译的正则表达式。regerror
:生成正则表达式错误消息。
函数详细介绍
1. regcomp
int regcomp(regex_t ®, const char *regex, int cflags);
功能:编译正则表达式,生成正则类型。
参数:
reg
:生成的正则类型,存储编译结果的regex_t
结构体。
regex
:要编译的正则表达式字符串。
cflags
:编译标志,默认填写0即可。返回值:如果成功,返回0;如果失败,返回非0值。
2. regexec
int regexec(const regex_t ®, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags);
功能:执行正则表达式匹配结果,调用一次匹配一条结果,循环调用匹配多条。
参数:
reg
:生成的正则类型,存储编译结果的regex_t
结构体
string
:要匹配的字符串。
nmatch
:正则表达式的数量例如:使用正则表达式
<name>\([^<]\+\?\)</name>
匹配用户名,这个正则表达式的提取规则数据的是<name></name>
,提取关键数据的是\([^<]\+\?\)
,一共两个表达式,因此正则数量就是2,即nmatch
为2。这个表达式也会匹配到name标签,不符合用户需求。那么我们需要对匹配到的结果进行偏移,偏移的大小由name标签位置决定,而表达式位置就存放在pmatch
内。
pmatch
:一个regmatch_t
结构数组,用于存储匹配的子字符串位置。数组的大小是表达式的数量,即nmatch
。结构体内存放的是每个表达式的起始位置和末尾位置。eflags
:执行标志,默认填写0即可。- 返回值:如果成功匹配,返回0;如果不匹配,返回
REG_NOMATCH
;如果发生其他错误,返回其他非0值。
3. regfree
void regfree(regex_t ®);
功能:释放由
regcomp
编译的正则表达式。参数:
reg
:存储编译结果的regex_t
结构体返回值:无。
正则函数的使用
下面我们将用这几个函数写一个读取name标签下用户名的示例程序:
有这样一个保存用户名的文本,用户名存放在name标签内,现在需要我们获取到所有用户名,不包含name标签。
1、确定正则表达式
我们先来试试用grep命令如何实现简单的正则匹配:
grep '<name>[^<]\+\?</name>' user_list
这里的?表示的是以非贪婪模式进行匹配。
可以正常匹配出结果,所以regcomp
函数内要传入的正则表达式就是<name>\\([^<]\\+\\?\\)</name>
(C语言代码中的\需要进行转义)
2、确定正则数量
正则表达式写完我们需要确实子表达式的数量,以此来确定nmatch
以及pmatch
数组的大小
<name>\\([^<]\\+\\?\\)</name>
这个正则表达式的子表达式数量是2
所以有nmatch=2; regmatch_t ma[2];//按正则数量定义长度
3、将文件数据加载到进程内存
有两种方法:
1)open函数打开文件->加载文件内容到buffer->从buffer中使用read函数读取内容, 经典的文件读取处理操作
2)映射,在进程中申请映射内存,直接将文件数据映射到内存中。需要计算文件大小。我们使用lseek
函数计算文件大小
这里我们采用映射的方法,更加方便。
4、生成正则类型
使用regcomp
函数编译正则表达式,生成正则类型保存到reg
中。
5、模式匹配
使用regexec
函数循环执行正则表达式匹配结果,使用snprintf
函数在正确位置读取数据,同时进行表达式位置偏移和映射地址的,筛选出正确结果。
示例程序
#include<unistd.h>
#include<sys/mman.h>
#include<string.h>
#include<regex.h>
#include<fcntl.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
int main(){
//1、确定正则表达式
char * reg_str="<name>\\([^<]\\+\\?\\)</name>";
//2、确定正则数量
regmatch_t ma[2];
//3、将文件数据加载到进程内存
//使用映射,在进程中读取内存,将文件数据映射到内存中
int fd=open("user_list",O_RDWR);//使用文件描述符记录文件
int size=lseek(fd,0,SEEK_END);//计算文件大小
//内存映射
char * ptr=mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);
printf("%s\n",ptr);
//4、生成正则类型
regex_t reg;
regcomp(®,reg_str,0);
//5、进行模式匹配
char data[1024];
bzero(data,sizeof(data));
while(regexec(®,ptr,2,ma,0)==0){
//根据位置读取数据
snprintf(data,ma[1].rm_eo-ma[1].rm_so+1,"%s",ptr+ma[1].rm_so);
printf("%s\n",data);
bzero(data,sizeof(data));
//偏移映射地址,避免重复处理一个结果
ptr+=ma[0].rm_eo;
}
return 0;
}
贪婪模式和非贪婪模式
正则表达式中的贪婪模式和非贪婪模式是两种不同的匹配行为。它们的主要区别在于匹配的优先级和结果的长度。
贪婪模式(Greedy)
- 定义:贪婪模式尽可能匹配尽可能多的字符。
- 表现:在匹配过程中,贪婪模式会扩展匹配的范围,直到遇到无法匹配的情况。
- 符号:常规的量词,如
*
,+
,?
,{m,n}
默认都是贪婪模式。
示例:
<.*>
- 字符串:
"<div>content</div>"
- 匹配结果:贪婪模式会匹配尽可能多的字符,因此它会匹配整个字符串,即
"<div>content</div>"
。
非贪婪模式(Non-Greedy or Lazy)
- 定义:非贪婪模式尽可能匹配尽可能少的字符。
- 表现:在匹配过程中,非贪婪模式会尽量缩小匹配的范围,直到遇到满足条件的情况。
- 符号:在贪婪量词后加上
?
,例如*?
,+?
,??
,{m,n}?
。
示例:
<.*?>
- 字符串:
"<div>content</div>"
- 匹配结果:非贪婪模式会匹配尽可能少的字符,因此它会匹配第一个
<div>
,即"<div>"
。
具体区别
贪婪模式:
尽量多匹配字符。
常用量词:
*
,+
,?
,{m,n}
。
a.*b
匹配字符串 "a123b456b"
中的 "a123b456b"
。
非贪婪模式:
尽量少匹配字符。
常用量词:
*?
,+?
,??
,{m,n}?
a.*?b
匹配字符串 "a123b456b"
中的 "a123b"
。
- 贪婪模式 尽量多匹配字符,适用于需要最大范围匹配的情况。
- 非贪婪模式 尽量少匹配字符,适用于需要最小范围匹配的情况。