如何在Linux下用C语言实现正则表达式?

99ANYc3cd6
预计阅读时长 25 分钟
位置: 首页 C语言 正文

在 Linux 中,C 语言处理正则表达式最标准、最常用的库是 POSIX 正则表达式库,其头文件是 <regex.h>,这个库提供了强大且灵活的模式匹配功能。

下面我将从核心概念、关键函数、完整代码示例、编译运行以及更现代的 PCRE 选项几个方面进行详细说明。


核心概念

在使用 <regex.h> 之前,你需要了解几个基本概念:

  1. 模式字符串:

    • 这就是你写的正则表达式,"^hello [0-9]+$"
    • 它需要被编译成一个内部使用的格式,这个过程叫做编译
  2. 编译后的模式:

    • 一个 regex_t 类型的结构体变量。
    • 你不能直接操作这个结构体,它是由 regcomp() 函数填充的,代表了编译后的正则表达式。
  3. 匹配字符串:

    • 你想要在其中查找模式的普通字符串,"hello 123 world"
  4. 匹配结果:

    • 一个 regmatch_t 类型的数组,用于存储匹配到的子串在原始字符串中的位置和长度。
    • regmatch_t 结构体包含两个成员:
      • rm_so: 子串的起始偏移量(从字符串开头算起的字符数)。
      • rm_eo: 子串的结束偏移量(指向最后一个字符的下一个位置)。

关键函数

POSIX 正则表达式库主要使用三个函数:regcomp(), regexec(), 和 regfree()

a. regcomp() - 编译正则表达式

这个函数将你的正则表达式字符串编译成一个优化的内部表示形式。

#include <regex.h>
int regcomp(regex_t *preg, const char *pattern, int cflags);
  • 参数:

    • preg: 指向 regex_t 结构体的指针,用于存储编译后的模式。
    • pattern: 你要编译的正则表达式字符串。
    • cflags: 编译标志,用于控制匹配行为,常用标志有:
      • REG_EXTENDED: 使用扩展的正则表达式语法(强烈推荐,功能更强大)。
      • REG_ICASE: 忽略大小写进行匹配。
      • REG_NOSUB: 如果你只关心“是否匹配”而不关心“匹配到了什么”,可以使用此标志来优化性能。
      • REG_NEWLINE: 将换行符 (\n) 视为普通字符,而不是字符串分隔符。
  • 返回值:

    • 成功返回 0
    • 失败返回一个非零的错误码,你可以用 regerror() 函数将这个错误码转换成可读的字符串。

b. regexec() - 执行匹配

这个函数使用编译后的模式去匹配一个给定的字符串。

#include <regex.h>
int regexec(const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags);
  • 参数:

    • preg: 指向由 regcomp() 编译后的 regex_t 结构体的指针。
    • string: 要被匹配的目标字符串。
    • nmatch: pmatch 数组的大小,如果你想知道所有匹配的子串,这个值应该足够大。
    • pmatch: 指向 regmatch_t 数组的指针,用于存储匹配结果。pmatch[0] 存储整个匹配的子串,pmatch[1] 存储第一个括号 匹配的子串,以此类推。
    • eflags: 执行标志,通常设置为 0,常用标志有:
      • REG_NOTBOL: 告诉引擎字符串的开始 (^) 不是行的开始。
      • REG_NOTEOL: 告诉引擎字符串的结束 () 不是行的结束。
  • 返回值:

    • REG_NOMATCH: 没有找到匹配项。
    • 0: 找到匹配项。
    • 其他负值: 表示发生了错误。

c. regfree() - 释放内存

当你使用完编译后的模式后,必须调用此函数来释放它所占用的内存。

#include <regex.h>
void regfree(regex_t *preg);
  • 参数:
    • preg: 指向由 regcomp() 创建的 regex_t 结构体的指针。

完整代码示例

下面是一个完整的 C 程序,演示了如何编译一个正则表达式,并用它来匹配字符串,最后打印出匹配到的结果。

regex_example.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <regex.h>
#define MAX_MATCHES 10
// 错误处理函数
void handle_error(int errcode, const regex_t *preg) {
    size_t len = regerror(errcode, preg, NULL, 0);
    char *error_msg = malloc(len * sizeof(char));
    regerror(errcode, preg, error_msg, len);
    fprintf(stderr, "Regex error: %s\n", error_msg);
    free(error_msg);
    exit(EXIT_FAILURE);
}
int main() {
    // 1. 定义正则表达式和待匹配的字符串
    const char *pattern = "^([a-zA-Z]+) ([0-9]+)$"; // 匹配 "单词 数字" 格式的行
    const char *test_strings[] = {
        "hello 123",
        "world 456",
        "C is fun 789",
        "invalid_string",
        "another one 10",
        "Test 99"
    };
    int num_tests = sizeof(test_strings) / sizeof(test_strings[0]);
    // 2. 编译正则表达式
    regex_t regex;
    int ret = regcomp(&regex, pattern, REG_EXTENDED);
    if (ret != 0) {
        handle_error(ret, &regex);
    }
    printf("Pattern: \"%s\"\n", pattern);
    printf("----------------------------------------\n");
    // 3. 循环测试字符串
    for (int i = 0; i < num_tests; i++) {
        const char *str_to_match = test_strings[i];
        regmatch_t matches[MAX_MATCHES];
        printf("Testing string: \"%s\"\n", str_to_match);
        // 4. 执行匹配
        ret = regexec(&regex, str_to_match, MAX_MATCHES, matches, 0);
        if (ret == 0) {
            // 匹配成功
            printf("  -> Match found!\n");
            // 打印匹配到的子串
            for (int j = 0; j < MAX_MATCHES && matches[j].rm_so != -1; j++) {
                if (matches[j].rm_so != -1) {
                    // 计算子串长度
                    int len = matches[j].rm_eo - matches[j].rm_so;
                    // 分配内存并复制子串
                    char *substring = malloc((len + 1) * sizeof(char));
                    strncpy(substring, str_to_match + matches[j].rm_so, len);
                    substring[len] = '\0'; // 确保字符串正确终止
                    printf("     Group %d: \"%s\" (from %d to %d)\n", 
                           j, substring, matches[j].rm_so, matches[j].rm_eo - 1);
                    free(substring);
                }
            }
        } else if (ret == REG_NOMATCH) {
            // 没有匹配
            printf("  -> No match found.\n");
        } else {
            // 发生错误
            handle_error(ret, &regex);
        }
        printf("\n");
    }
    // 5. 释放编译后的正则表达式
    regfree(&regex);
    return 0;
}

在 Linux 上编译和运行

  1. 保存代码: 将上面的代码保存为 regex_example.c

  2. 编译: 使用 gcc 进行编译。<regex.h> 是标准库,所以不需要特殊的链接选项。

    gcc -o regex_example regex_example.c
    • -o regex_example: 指定输出的可执行文件名为 regex_example
  3. 运行: 执行生成的可执行文件。

    ./regex_example

预期输出:

Pattern: "^([a-zA-Z]+) ([0-9]+)$"
----------------------------------------
Testing string: "hello 123"
  -> Match found!
     Group 0: "hello 123" (from 0 to 8)
     Group 1: "hello" (from 0 to 4)
     Group 2: "123" (from 6 to 8)
Testing string: "world 456"
  -> Match found!
     Group 0: "world 456" (from 0 to 9)
     Group 1: "world" (from 0 to 5)
     Group 2: "456" (from 7 to 9)
Testing string: "C is fun 789"
  -> No match found.
Testing string: "invalid_string"
  -> No match found.
Testing string: "another one 10"
  -> No match found.
Testing string: "Test 99"
  -> Match found!
     Group 0: "Test 99" (from 0 to 7)
     Group 1: "Test" (from 0 to 4)
     Group 2: "99" (from 6 to 7)

更强大的选择:PCRE (Perl Compatible Regular Expressions)

POSIX <regex.h> 功能强大,但它的语法和特性可能不如 Perl 的正则表达式丰富,在 Linux 生态中,PCRE 库是事实上的标准,它提供了更现代、更强大的正则表达式功能。

为什么选择 PCRE?

  • 更丰富的特性: 支持非贪婪匹配 (, )、正向/反向预查 (, )、条件表达式等。
  • 性能: 通常比 POSIX 正则表达式有更好的性能。
  • 熟悉度高: 如果你熟悉 Perl、PHP、Python 等语言的正则表达式,你会很快上手 PCRE。

如何使用 PCRE?

PCRE 的 API 与 POSIX 有些不同,但基本流程相似:

  1. 安装 PCRE 开发库: 在基于 Debian/Ubuntu 的系统上:

    sudo apt-get update
    sudo apt-get install libpcre3-dev

    在基于 RedHat/CentOS 的系统上:

    sudo yum install pcre-devel
  2. 修改代码: 包含 <pcre.h> 头文件,并使用 PCRE 的函数(如 pcre_compile(), pcre_exec() 等)。

  3. 编译时链接 PCRE 库:

    gcc -o pcre_example pcre_example.c -lpcre
    • -lpcre: 告诉链接器去链接 PCRE 库。

PCRE 的 API 更复杂一些,因为它需要你手动处理模式字符串的选项、编译错误信息、以及一个用于 pcre_exec() 的额外上下文结构体 pcre_extra,但对于需要复杂正则表达式的项目来说,这是一个值得的投资。

特性 POSIX <regex.h> PCRE
标准 POSIX 标准 事实上的行业标准
功能 基础到中等 非常强大,接近 Perl
语法 扩展语法 (REG_EXTENDED) Perl 兼容语法
API 相对简单 (regcomp, regexec) 更复杂 (pcre_compile, pcre_exec)
性能 一般 通常更好
依赖 系统自带 需要安装 libpcre3-dev 等开发包
编译 gcc file.c gcc file.c -lpcre

对于初学者或者简单的匹配任务,POSIX <regex.h> 是一个很好的起点,如果你的项目需要处理复杂的文本模式,或者你对性能有较高要求,强烈建议学习和使用 PCRE

-- 展开阅读全文 --
头像
dede编辑后为何显示空白?
« 上一篇 今天
织梦多条件搜索功能如何实现高效精准匹配?
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

目录[+]