正则表达式
正则表达式(Regular Expression)系统总结
一、正则表达式的诞生历史
-
起源阶段(1950s-1960s)
- 理论基础:数学家Stephen Kleene提出正则集合(Regular Sets),描述有限自动机识别的语言。
- 计算机应用:Ken Thompson(Unix之父)在1968年将其引入QED文本编辑器,用于文本搜索。
-
工具普及阶段(1970s-1980s)
- Unix工具链:
ed
→ grep
→ sed
→ awk
逐步将正则表达式推广为系统级工具。
- 标准化:POSIX标准定义了BRE(Basic Regular Expressions)和ERE(Extended Regular Expressions)。
-
现代扩展(1990s至今)
- Perl语言:引入PCRE(Perl Compatible Regular Expressions),支持更复杂的语法(如非贪婪匹配、零宽断言)。
- 多语言支持:Java、Python、JavaScript等主流语言均内置正则引擎,语法逐渐统一。
二、正则表达式核心概念
1. 模式串的组成元素
类别 |
符号/语法 |
说明 |
普通字符 |
a , 1 , @ |
匹配字面值字符 |
元字符 |
. ^ $ * + ? |
具有特殊含义的字符,需转义(如\. 匹配点号) |
字符类 |
[abc] , [^a-z] |
匹配指定字符集合中的任意一个字符 |
预定义字符类 |
\d , \w , \s |
匹配数字(\d )、单词字符(\w )、空白符(\s ) |
量词 |
{n} , {n,} , {n,m} |
指定匹配次数(如a{3} 匹配aaa ) |
分组与捕获 |
(pattern) |
将子模式分组,并捕获匹配内容 |
非捕获分组 |
(?:pattern) |
分组但不捕获 |
分支选择 |
a|b |
匹配a 或b |
2. 边界匹配
符号 |
作用 |
^ |
匹配字符串开始(或行首) |
$ |
匹配字符串结束(或行尾) |
\b |
匹配单词边界(字母与非字母间) |
\B |
匹配非单词边界 |
3. 转义字符
符号 |
实际匹配的字符 |
\\ |
反斜杠 \ |
\. |
点号 . |
\* |
星号 * |
\t |
制表符 |
\n |
换行符 |
4. 贪婪匹配 vs 非贪婪匹配
- 贪婪模式:默认行为,量词会尽可能多匹配字符。
示例:a.*b
匹配 axxxbxxb
中的整个字符串。
- 非贪婪模式:在量词后加
?
,尽可能少匹配字符。
示例:a.*?b
匹配 axxxbxxb
中的 axxxb
。
5. 零宽断言(Lookaround)
类型 |
语法 |
作用 |
正向先行断言 |
(?=...) |
匹配后面跟随指定模式的位置 |
负向先行断言 |
(?!...) |
匹配后面不跟随指定模式的位置 |
正向后行断言 |
(?<=...) |
匹配前面有指定模式的位置(部分引擎支持) |
负向后行断言 |
(?<!...) |
匹配前面无指定模式的位置(部分引擎支持) |
示例:
- \d+(?=px)
匹配后面是px
的数字(如16px
中的16
)。
- (?<!-)\d+
匹配前面没有负号的数字。
三、正则表达式流派对比
特性 |
POSIX BRE |
POSIX ERE |
PCRE(Perl风格) |
量词默认行为 |
需转义(\+ ) |
直接使用(+ ) |
直接使用(+ ) |
非贪婪匹配 |
不支持 |
不支持 |
支持(*? +? ) |
零宽断言 |
不支持 |
不支持 |
支持 |
预定义字符类 |
有限支持 |
有限支持 |
支持(如\d ) |
引擎性能 |
较低 |
中等 |
较高 |
四、常见使用场景
- 文本验证
- 邮箱:
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
- 手机号:
^1[3-9]\d{9}$
- 文本提取
- 提取HTML标签内容:
<a[^>]*>(.*?)</a>
- 抓取日志中的IP地址:
\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}
- 文本替换
- 替换日期格式:
(\d{4})-(\d{2})-(\d{2})
→ $2/$3/$1
- 删除多余空格:
\s+
→
(替换为单个空格)
五、正则表达式调试与优化
- 调试工具
- 常见陷阱
- 回溯失控:复杂模式导致性能骤降,避免嵌套量词(如
(a+)+
)。
- 贪婪匹配错误:误用
.*
导致匹配过多内容,优先使用非贪婪模式(.*?
)。
- 字符集遗漏:如
[A-Z]
未包含小写字母,可添加[A-Za-z]
。
六、经典学习路径
- 基础语法:掌握元字符、量词、字符类、分组。
- 实战练习:从简单匹配(如电话号码)到复杂文本处理。
- 高级特性:学习零宽断言、非捕获分组、条件匹配。
- 引擎原理:了解DFA/NFA引擎差异,优化正则性能。
总结
正则表达式是文本处理的瑞士军刀,诞生于理论数学,成长于Unix工具链,成熟于现代编程语言。核心模式串通过元字符、量词、分组等元素构建匹配规则,需注意不同流派(如POSIX/PCRE)的语法差异。掌握正则表达式可大幅提升文本处理效率,但需警惕性能陷阱,合理使用工具调试优化。
POSIX下C语言正则表达式开发总结
1. 头文件与核心结构
- 头文件:
<regex.h>
- 核心结构:
typedef struct {
size_t re_nsub; // 子表达式数量
// 内部实现细节(不直接访问)
} regex_t;
typedef struct {
regoff_t rm_so; // 匹配起始偏移
regoff_t rm_eo; // 匹配结束偏移(不包含)
} regmatch_t;
2. 核心函数
函数 |
功能描述 |
参数说明 |
regcomp |
编译正则表达式为regex_t 结构 |
regex_t *preg , const char *regex , int cflags |
regexec |
执行正则匹配 |
const regex_t *preg , const char *string , size_t nmatch , regmatch_t pmatch[] , int eflags |
regerror |
转换错误码为可读信息 |
int errcode , const regex_t *preg , char *errbuf , size_t errbuf_size |
regfree |
释放regex_t 资源 |
regex_t *preg |
3. 正则表达式语法类型
- BRE (Basic Regular Expressions)
- 元字符需转义(如
\( \)
, \{ \}
, \+
)
- 默认贪婪匹配
- ERE (Extended Regular Expressions)
- 元字符直接使用(如
()
, {}
, +
)
- 通过
regcomp
的REG_EXTENDED
标志启用
4. 编译标志 (cflags
)
标志 |
作用 |
REG_EXTENDED |
使用ERE语法(默认BRE) |
REG_ICASE |
忽略大小写 |
REG_NOSUB |
不报告子表达式匹配位置(优化性能) |
REG_NEWLINE |
使. 不匹配换行符,^ /$ 匹配行首/尾 |
5. 执行标志 (eflags
)
标志 |
作用 |
REG_NOTBOL |
字符串起始不作为行首(^ 无效) |
REG_NOTEOL |
字符串结束不作为行尾($ 无效) |
6. 错误处理
- 常见错误码:
REG_NOMATCH
: 无匹配
REG_BADPAT
: 无效模式
REG_ESPACE
: 内存不足
- 错误信息获取:
char errbuf[100];
regerror(errcode, &preg, errbuf, sizeof(errbuf));
7. 匹配结果处理
regmatch_t
数组:存储匹配位置(索引0为整个匹配,1~n为子表达式)
regmatch_t pmatch[3]; // 假设有2个子表达式
if (regexec(&preg, input, 3, pmatch, 0) == 0) {
for (int i = 0; i < 3; i++) {
printf("Match %d: [%d-%d]\n", i, pmatch[i].rm_so, pmatch[i].rm_eo);
}
}
8. 线程安全
- 不可重入性:同一
regex_t
实例在多线程中需同步访问。
- 推荐实践:每个线程独立编译
regex_t
实例。
9. 示例代码
#include <regex.h>
#include <stdio.h>
int main() {
regex_t preg;
const char *pattern = "([a-z]+)\\.([a-z]+)";
const char *input = "example.com";
regmatch_t pmatch[3];
if (regcomp(&preg, pattern, REG_EXTENDED) != 0) {
fprintf(stderr, "Compilation error\n");
return 1;
}
if (regexec(&preg, input, 3, pmatch, 0) == 0) {
printf("Full match: %.*s\n", pmatch[0].rm_eo - pmatch[0].rm_so, input + pmatch[0].rm_so);
printf("Sub 1: %.*s\n", pmatch[1].rm_eo - pmatch[1].rm_so, input + pmatch[1].rm_so);
printf("Sub 2: %.*s\n", pmatch[2].rm_eo - pmatch[2].rm_so, input + pmatch[2].rm_so);
} else {
printf("No match\n");
}
regfree(&preg);
return 0;
}
10. 注意事项
- 资源释放:务必调用
regfree
避免内存泄漏。
- 性能:预编译常用正则表达式提升效率。
- 兼容性:POSIX正则与其他库(如PCRE)语法差异,避免混用。
总结
POSIX正则提供标准化的文本匹配接口,通过regcomp
、regexec
等函数实现编译与匹配,支持BRE/ERE语法。开发时需注意线程安全、错误处理及资源管理,合理使用子表达式和标志以优化匹配逻辑。