前言:正則,愛恨交織的利器
正則表達式是文本處理的瑞士軍刀,強大卻晦澀。PHP 提供兩套正則引擎:PCRE (Perl Compatible Regular Expressions) 與已棄用的 POSIX (ereg_*)。自 PHP 5.3 起 POSIX 正式棄用,PHP 7/8 已完全移除。本文聚焦 PCRE,系統梳理核心概念、常用元字符、修飾符、分組引用,並附贈高頻實戰片段,助你從「正則小白」進階為「模式匹配達人」。
PCRE 基礎語法速查
定界符與修飾符
PCRE 模式必須被一對定界符包裹,常用 /.../、#...#、~...~。修飾符緊隨結尾定界符之後:
| 修飾符 | 名稱 | 作用 | 典型場景 |
i | PCRE_CASELESS | 大小寫不敏感 | 關鍵詞搜索、用戶輸入校驗 |
m | PCRE_MULTILINE | ^/$ 匹配每行首尾 | 多行文本逐行解析 |
s | PCRE_DOTALL | . 匹配換行符 | 跨標簽提取 HTML 內容 |
x | PCRE_EXTENDED | 忽略模式中的空白與註釋 | 編寫可讀性強的複雜正則 |
u | PCRE_UTF8 | 按 UTF-8 碼點匹配 | 中文/多語言匹配必加 |
U | PCRE_UNGREEDY | 反轉貪婪/非貪婪模式 | 配合 ? 精確控制匹配長度 |
e | PREG_REPLACE_EVAL | 已移除 (PHP 7+) | 請改用 preg_replace_callback() |
核心元字符速查表
| 元字符 | 含義 | 等價類 | 示例 |
\A | 字符串絕對開頭 | - | /\Aphp/ 僅匹配開頭 |
\Z | 字符串絕對結尾(含末尾換行) | - | /php\Z/ |
\z | 字符串絕對結尾(不含換行) | - | 更嚴格的結尾錨點 |
\b | 單詞邊界 | - | /\bcat\b/ 匹配單詞 cat |
\B | 非單詞邊界 | - | /\Bcat\B/ 匹配 scatter 中的 cat |
\d | 數字 | [0-9] | /\d{3}-\d{8}/ 電話號 |
| \D | 非數字 | [^0-9] | - |
\w | 單詞字符(字母/數字/下劃線) | [a-zA-Z0-9_] | 變量名校驗 |
\W | 非單詞字符 | [^a-zA-Z0-9_] | - |
\s | 空白字符 | [\f\n\r\t\v] | /\s+/ 匹配多空白 |
\S | 非空白字符 | [^\f\n\r\t\v] | - |
. | 任意字符(除換行,s 修飾符下含換行) | - | /a.b/ 匹配 axb, ab |
量詞與貪婪/非貪婪模式
| 量詞 | 含義 | 貪婪(默認) | 非貪婪(加?) |
* | 0 次或多次 | .* 匹配盡可能多 | .*? 匹配盡可能少 |
+ | 1 次或多次 | .+ | .+? |
? | 0 次或 1 次 | .? | .?? |
{n} | 恰好 n 次 | \d{3} | - |
{n,} | 至少 n 次 | \d{2,} | \d{2,}? |
{n,m} | n 到 m 次 | \d{3,5} | \d{3,5}? |
分組、捕獲與反向引用
捕獲組 vs 非捕獲組
$pattern = '/(\d{4})-(\d{2})-(\d{2})/'; // 捕獲組:$1=年, $2=月, $3=日
$pattern = '/(?:\d{4})-(\d{2})-(\d{2})/'; // (?:...) 非捕獲組:$1=月, $2=日
$pattern = '/(?\d{4})-(?\d{2})/'; // 命名捕獲組 (PHP 5.2.2+): $matches['year']
反向引用:\1, \2, \k<name>
["\'])(.*?)\k/'; // 命名引用
PCRE 核心函數族詳解
| 函數 | 用途 | 返回值 | 關鍵參數 |
preg_match() | 執行單次匹配 | int (0/1/false),$matches 存捕獲結果 | $pattern, $subject, &$matches, $flags, $offset |
preg_match_all() | 全局匹配所有 | int 匹配次數,$matches 結構由 PREG_PATTERN_ORDER/PREG_SET_ORDER 控制 | 同上 |
preg_replace() | 正則替換 | 替換後字符串/數組 | $pattern, $replacement, $subject, $limit, &$count |
preg_replace_callback() | 回調替換(推薦替代 /e) | 替換後字符串 | callable $callback 接收 $matches |
preg_split() | 正則分割字符串 | 數組 | $pattern, $subject, $limit, $flags |
preg_grep() | 正則過濾數組元素 | 匹配的元素數組 | $pattern, $input, $flags |
preg_quote() | 轉義正則特殊字符 | 轉義後字符串 | $str, $delimiter = null |
preg_last_error() | 獲取最後錯誤碼 | int (PREG_NO_ERROR 等) | 調試必備 |
高頻實戰片段庫
1. 提取 HTML 標簽屬性值 (action/src/href)
$html = '<form action="/login" method="post"><img src="logo.png">';
preg_match_all('/\s(action|src|href)\s*=\s*["\']([^"\']+)["\']/i', $html, $m);
// $m[2] = ['/login', 'logo.png']
2. 回調函數動態重寫鏈接 (補全域名)
3. 斷言提取無屬性標簽內容
bold 這類無屬性標簽的內容
$str = '<b>bold</b> <span class="x">no</span>';
preg_match_all('/(?<=<(\w+)>).*?(?=<\/\1>)/s', $str, $m);
// $m[0] = ['bold']
4. 驗證身份證號 (簡化版,含校驗碼邏輯需額外實現)
5. 匹配中文/多語言 (必加 u 修飾符)
性能優化與避坑指南
- 能用
strpos/explode/str_replace 解決的,絕不用正則 — 字符串函數快 10-100 倍。
- 避免災難性回溯:
(a+)+ 匹配 aaaaaaaaaaaa! 會指數級爆炸。使用 ++ 佔有優先量詞或原子組 (?>...) 斷路。
- 預編譯緩存:長期運行進程 (Swoole/RoadRunner) 中,將模式存入靜態變量,避免重複編譯。
- 大數據分塊處理:
preg_match_all 返回巨大數組會撐爆內存,改用 preg_split + 生成器流式處理。
- JIT 加速 (PHP 8+):開啟
pcre.jit=1 (默認開啟),複雜正則可獲 2-5 倍提速。
結語
正則表達式是把雙刃劍:用得好是「一行代碼解決萬行邏輯」,用不好是「寫時只懂我與天,改時只懂天」。掌握 PCRE 核心語法、善用修飾符與斷言、養成「先想字符串函數能不能搞定」的習慣,才能真正駕馭這把利器。建議收藏本文速查表,實戰中多練 preg_replace_callback 與 u 修飾符,處理多語言文本將不再是噩夢。