C 標準庫不內建正則表達式,主流方案有二:
本文以 POSIX 為基礎,補充 PCRE 差異,給出完整編譯、匹配、擷取、替換範例。
| 函式 | 用途 |
|---|---|
| `regcomp(regex_t *preg, const char *pattern, int cflags)` | 編譯模式 → `regex_t` 結構 |
| `regexec(const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags)` | 匹配字串,填充 `pmatch` 擷取群組 |
| `regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size)` | 錯誤碼轉人類可讀訊息 |
| `regfree(regex_t *preg)` | 釋放編譯後資源 |
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <regex.h>
#define MAX_MATCH 10
int main(void) {
const char *request = "GET /api/user?id=123 HTTP/1.1";
const char *pattern = "^([A-Z]+) ([^ ]+) HTTP/([0-9.]+)$";
regex_t regex;
regmatch_t matches[MAX_MATCH];
char errbuf[256];
/* 1. 編譯 */
int rc = regcomp(®ex, pattern, REG_EXTENDED);
if (rc != 0) {
regerror(rc, ®ex, errbuf, sizeof(errbuf));
fprintf(stderr, "regcomp 失敗: %s\n", errbuf);
return 1;
}
/* 2. 匹配 */
rc = regexec(®ex, request, MAX_MATCH, matches, 0);
if (rc == 0) {
printf("匹配成功!\n");
for (size_t i = 0; i < MAX_MATCH && matches[i].rm_so != -1; ++i) {
int start = matches[i].rm_so;
int len = matches[i].rm_eo - matches[i].rm_so;
printf(" 群組 %zu: [%.*s]\n", i, len, request + start);
}
} else if (rc == REG_NOMATCH) {
printf("不匹配\n");
} else {
regerror(rc, ®ex, errbuf, sizeof(errbuf));
fprintf(stderr, "regexec 錯誤: %s\n", errbuf);
}
/* 3. 釋放 */
regfree(®ex);
return 0;
}
匹配成功! 群組 0: [GET /api/user?id=123 HTTP/1.1] 群組 1: [GET] 群組 2: [/api/user?id=123] 群組 3: [1.1]
const char *text = "ab12cd34ef56";
const char *pat = "[0-9]+";
regex_t regex;
regcomp(®ex, pat, REG_EXTENDED);
regmatch_t match;
const char *p = text;
while (regexec(®ex, p, 1, &match, 0) == 0) {
printf("找到數字: [%.*s] 位置 %ld\n",
match.rm_eo - match.rm_so, p + match.rm_so, p - text + match.rm_so);
p += match.rm_eo; // 移至匹配後繼續
}
regfree(®ex);
POSIX 無內建替換,需自行:匹配 → 複製前綴 → 附加替換字串 → 移動指標 → 重複 → 複製尾綴。
`regcomp` 較耗時,相同模式多次匹配時,編譯一次、全域/靜態快取 `regex_t`、多執行緒需鎖保護。
gcc -o pcre_demo pcre_demo.c -lpcre
| 特性 | POSIX | PCRE |
|---|---|---|
| 語法 | BRE/ERE | Perl 相容 (支援 `\d`、`\w`、`(?=...)`、`(?!...)`、`\1` 回溯引用、`(?(cond)yes|no)` 條件分支) |
| API 風格 | `regcomp`/`regexec` | `pcre_compile`/`pcre_exec`/`pcre_free` |
| 效能 | NFA 回溯,較慢 | 優化 NFA + JIT 可選,快 5-10 倍 |
| Unicode | 需 `REG_UTF8` (GNU 延伸) | 原生支援 `PCRE_UTF8` |
#include <pcre.h>
#include <stdio.h>
int main(void) {
const char *pat = "(?<year>\\d{4})-(?<mon>\\d{2})-(?<day>\\d{2})";
const char *subj = "2025-05-14";
const char *err; int erroff;
pcre *re = pcre_compile(pat, PCRE_UTF8, &err, &erroff, NULL);
if (!re) { fprintf(stderr, "編譯失敗: %s at %d\n", err, erroff); return 1; }
int ovector[30];
int rc = pcre_exec(re, NULL, subj, strlen(subj), 0, 0, ovector, 30);
if (rc > 0) {
for (int i = 0; i < rc; ++i) {
const char *name; int n;
pcre_get_named_substring(re, subj, ovector, rc, NULL, &name, &n); // 簡化
printf("群組 %d: [%.*s]\n", i, ovector[2*i+1]-ovector[2*i], subj+ovector[2*i]);
}
}
pcre_free(re);
return 0;
}
C 做正則表達式,「POSIX 輕量零依賴、PCRE 功能強效能高」。根據專案依賴預算與模式複雜度選型,並務必建立「編譯一次、多次匹配、錯誤必檢」的工程化規範。