跳转至

正则表达式

正则表达式(Regular Expression)系统总结


一、正则表达式的诞生历史

  1. 起源阶段(1950s-1960s)

    • 理论基础:数学家Stephen Kleene提出正则集合(Regular Sets),描述有限自动机识别的语言。
    • 计算机应用:Ken Thompson(Unix之父)在1968年将其引入QED文本编辑器,用于文本搜索。
  2. 工具普及阶段(1970s-1980s)

    • Unix工具链edgrepsedawk 逐步将正则表达式推广为系统级工具。
    • 标准化:POSIX标准定义了BRE(Basic Regular Expressions)ERE(Extended Regular Expressions)
  3. 现代扩展(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 匹配ab
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
引擎性能 较低 中等 较高

四、常见使用场景

  1. 文本验证
    • 邮箱:^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
    • 手机号:^1[3-9]\d{9}$
  2. 文本提取
    • 提取HTML标签内容:<a[^>]*>(.*?)</a>
    • 抓取日志中的IP地址:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}
  3. 文本替换
    • 替换日期格式:(\d{4})-(\d{2})-(\d{2})$2/$3/$1
    • 删除多余空格:\s+(替换为单个空格)

五、正则表达式调试与优化

  1. 调试工具
    • 在线测试:Regex101RegExr
    • IDE插件:VS Code、IntelliJ内置正则调试。
  2. 常见陷阱
    • 回溯失控:复杂模式导致性能骤降,避免嵌套量词(如(a+)+)。
    • 贪婪匹配错误:误用.*导致匹配过多内容,优先使用非贪婪模式(.*?)。
    • 字符集遗漏:如[A-Z]未包含小写字母,可添加[A-Za-z]

六、经典学习路径

  1. 基础语法:掌握元字符、量词、字符类、分组。
  2. 实战练习:从简单匹配(如电话号码)到复杂文本处理。
  3. 高级特性:学习零宽断言、非捕获分组、条件匹配。
  4. 引擎原理:了解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)
  • 元字符直接使用(如(), {}, +
  • 通过regcompREG_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. 注意事项

  1. 资源释放:务必调用regfree避免内存泄漏。
  2. 性能:预编译常用正则表达式提升效率。
  3. 兼容性:POSIX正则与其他库(如PCRE)语法差异,避免混用。

总结

POSIX正则提供标准化的文本匹配接口,通过regcompregexec等函数实现编译与匹配,支持BRE/ERE语法。开发时需注意线程安全、错误处理及资源管理,合理使用子表达式和标志以优化匹配逻辑。