在 Linux 中,C 语言处理正则表达式最标准、最常用的库是 POSIX 正则表达式库,其头文件是 <regex.h>,这个库提供了强大且灵活的模式匹配功能。
下面我将从核心概念、关键函数、完整代码示例、编译运行以及更现代的 PCRE 选项几个方面进行详细说明。
核心概念
在使用 <regex.h> 之前,你需要了解几个基本概念:
-
模式字符串:
- 这就是你写的正则表达式,
"^hello [0-9]+$"。 - 它需要被编译成一个内部使用的格式,这个过程叫做编译。
- 这就是你写的正则表达式,
-
编译后的模式:
- 一个
regex_t类型的结构体变量。 - 你不能直接操作这个结构体,它是由
regcomp()函数填充的,代表了编译后的正则表达式。
- 一个
-
匹配字符串:
- 你想要在其中查找模式的普通字符串,
"hello 123 world"。
- 你想要在其中查找模式的普通字符串,
-
匹配结果:
- 一个
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(®ex, pattern, REG_EXTENDED);
if (ret != 0) {
handle_error(ret, ®ex);
}
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(®ex, 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, ®ex);
}
printf("\n");
}
// 5. 释放编译后的正则表达式
regfree(®ex);
return 0;
}
在 Linux 上编译和运行
-
保存代码: 将上面的代码保存为
regex_example.c。 -
编译: 使用
gcc进行编译。<regex.h>是标准库,所以不需要特殊的链接选项。gcc -o regex_example regex_example.c
-o regex_example: 指定输出的可执行文件名为regex_example。
-
运行: 执行生成的可执行文件。
./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 有些不同,但基本流程相似:
-
安装 PCRE 开发库: 在基于 Debian/Ubuntu 的系统上:
sudo apt-get update sudo apt-get install libpcre3-dev
在基于 RedHat/CentOS 的系统上:
sudo yum install pcre-devel
-
修改代码: 包含
<pcre.h>头文件,并使用 PCRE 的函数(如pcre_compile(),pcre_exec()等)。 -
编译时链接 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。
