探秘正则表达式:从grep命令到C语言

 🔥博客主页: 我要成为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正则表达式库提供了一组函数,用于处理正则表达式。下面是这些函数的简要介绍:

  1. regcomp:编译正则表达式。
  2. regexec:执行正则表达式匹配。
  3. regfree:释放由 regcomp 编译的正则表达式。
  4. regerror:生成正则表达式错误消息。

函数详细介绍

1. regcomp

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

功能:编译正则表达式,生成正则类型。

参数

reg:生成的正则类型,存储编译结果的 regex_t 结构体。

regex:要编译的正则表达式字符串。

cflags:编译标志,默认填写0即可。

返回值:如果成功,返回0;如果失败,返回非0值。

2. regexec

int regexec(const regex_t &reg, 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 &reg);

功能:释放由 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,reg_str,0);

    //5、进行模式匹配
    char data[1024];
    bzero(data,sizeof(data));

    while(regexec(&reg,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"

  • 贪婪模式 尽量多匹配字符,适用于需要最大范围匹配的情况。
  • 非贪婪模式 尽量少匹配字符,适用于需要最小范围匹配的情况。

  • 31
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值