熟悉Linux sed/awk/grep/vi 的開發者對正則表達式絕不陌生。它以聲明式模式極大簡化了複雜文本處理。雖然標準C/C++庫未內置正則支持,但POSIX Regex (regex.h) 和 PCRE (Perl-Compatible Regular Expressions) 等成熟庫填補了這一空白。本文系統講解POSIX Regex API在C語言中的完整用法:編譯、匹配、提取子串、錯誤處理、資源釋放,並給出可直接編譯運行的完整示例。
regcomp() — 編譯正則表達式int regcomp(regex_t *preg, const char *regex, int cflags);
preg:輸出參數,指向 regex_t 結構,存儲編譯後的內部表示(狀態機)。regex:正則表達式字符串(如 "regex[a-z]*")。cflags:編譯標誌位(按位或):
REG_EXTENDED:使用擴展正則語法(默認基礎語法較弱)。REG_ICASE:忽略大小寫。REG_NOSUB:不保存匹配位置(僅判斷是否匹配,加速)。REG_NEWLINE:. 不匹配換行符,^/$ 匹配行首尾。0 成功,非零為錯誤碼(需配合 regerror 解析)。regexec() — 執行模式匹配int regexec(const regex_t *preg, const char *string,
size_t nmatch, regmatch_t pmatch[], int eflags);
string:待匹配文本。nmatch / pmatch:最多捕獲 nmatch 個子表達式匹配位置,存入 pmatch 數組。regmatch_t 結構:{ regoff_t rm_so; // 起始偏移 regoff_t rm_eo; // 結束偏移(不含) }pmatch[0] 為整個正則匹配範圍,pmatch[1..] 為捕獲組(括號內子表達式)。eflags:REG_NOTBOL (行首不視為行首)、REG_NOTEOL (行尾不視為行尾)。0 匹配成功,REG_NOMATCH 未匹配,其他為錯誤。regfree() — 釋放編譯資源void regfree(regex_t *preg);
必須在不再使用 preg 時調用,避免內存洩漏。最佳實踐:每次 regcomp 後配對一次 regfree,即使標準未強制要求。
regerror() — 獲取人類可讀錯誤信息size_t regerror(int errcode, const regex_t *preg,
char *errbuf, size_t errbuf_size);
errcode:regcomp/regexec 返回的非零錯誤碼。preg:提供上下文(如錯誤位置)。errbuf (最多 errbuf_size 字節),返回實際長度。#include <stdio.h>
#include <sys/types.h>
#include <regex.h>
#include <string.h>
// 截取子串輔助函數
static char* substr(const char* str, unsigned start, unsigned end) {
static char buf[256];
unsigned n = end - start;
strncpy(buf, str + start, n);
buf[n] = '\0';
return buf;
}
int main(int argc, char** argv) {
if (argc < 2) {
fprintf(stderr, "Usage: %s [file...]\n", argv[0]);
return 1;
}
char *pattern = argv[1];
regex_t reg;
regmatch_t pm[10];
const size_t nmatch = 10;
char ebuf[128], lbuf[1024];
int lno = 0, z;
// 1. 編譯正則
z = regcomp(®, pattern, REG_EXTENDED);
if (z != 0) {
regerror(z, ®, ebuf, sizeof(ebuf));
fprintf(stderr, "%s: pattern '%s'\n", ebuf, pattern);
return 1;
}
// 2. 逐行處理標準輸入
while (fgets(lbuf, sizeof(lbuf), stdin)) {
++lno;
// 去除尾部換行符
if ((z = strlen(lbuf)) > 0 && lbuf[z-1] == '\n')
lbuf[z-1] = '\0';
// 3. 執行匹配
z = regexec(®, lbuf, nmatch, pm, 0);
if (z == REG_NOMATCH) continue;
if (z != 0) {
regerror(z, ®, ebuf, sizeof(ebuf));
fprintf(stderr, "%s: regexec('%s')\n", ebuf, lbuf);
regfree(®);
return 2;
}
// 4. 輸出結果:行號 + 原行 + 所有捕獲組
for (int x = 0; x < nmatch && pm[x].rm_so != -1; ++x) {
if (!x) printf("%04d: %s\n", lno, lbuf);
printf(" $%d='%s'\n", x, substr(lbuf, pm[x].rm_so, pm[x].rm_eo));
}
}
// 5. 釋放資源
regfree(®);
return 0;
}
# gcc regexp.c -o regexp -lregex # 鏈接 regex 庫
# ./regexp 'regex[a-z]*' < regexp.c
0003: #include <regex.h>
$0='regex'
0027: regex_t reg;
$0='regex'
0054: z = regexec(®, lbuf, nmatch, pm, 0);
$0='regexec'
...
程序從標準輸入讀取文本(此處為自身源碼),對每行應用正則 regex[a-z]*,輸出匹配行及所有捕獲組($0為整體匹配)。
pmatch 數組大小需 ≥ 捕獲組數 + 1。動態分配可用 malloc(nmatch * sizeof(regmatch_t))。^/$ 匹配整字符串首尾。加 REG_NEWLINE 使其匹配行首尾。regfree。\d, \w, (?=...), (?>...)) 可改用 pcre.h,API風格相似但功能更豐富。regex_t 非線程安全,多線程環境下每線程獨立編譯或加鎖。POSIX Regex API 雖然接口略顯古老,但穩定、標準、無第三方依賴,是C語言項目中引入正則處理能力的首選。掌握 regcomp → regexec → regfree 三部曲,配合 regerror 診斷錯誤,即可在C項目中享受接近Perl/Python的文本處理靈活性。