從字串 "你好<我>(愛)[北京]{天安門}" 中分別提取四種括號內的內容。看似簡單,實則暗藏正則貪婪、巢狀、轉義、多行等陷阱。
<?php
$str = '你好<我>(愛)[北京]{天安門}';
$patterns = [
'angle' => '/<([^>]*)>/', // 尖括號
'round' => '/\(([^)]*)\)/', // 圓括號
'square' => '/\[([^\]]*)\]/', // 方括號
'curly' => '/\{([^}]*)\}/', // 大括號
];
foreach ($patterns as $name => $pattern) {
if (preg_match($pattern, $str, $m)) {
echo "$name: {$m[1]}\n";
}
}
/*
angle: 我
round: 愛
square: 北京
curly: 天安門
*/
PHP PCRE 支援 (?R) 遞歸引用,可匹配平衡巢狀括號:
<?php
$str = 'func(a, (b, c), d)';
// 匹配最外層圓括號內容(含巢狀)
$pattern = '/\(((?:[^()]+|(?R))*)\)/';
if (preg_match($pattern, $str, $m)) {
echo "Outer: {$m[1]}\n"; // a, (b, c), d
}
// 提取所有層級
preg_match_all($pattern, $str, $matches);
print_r($matches[1]);
/*
Array
(
[0] => a, (b, c), d
[1] => b, c
)
*/
<?php
/**
* 提取括號內容
*
* @param string $str 來源字串
* @param string $bracket 'angle'|'round'|'square'|'curly'|'all'
* @param bool $nested 是否支援巢狀(遞歸正則,較慢)
* @param bool $multiple 是否返回所有匹配
* @return array|string|null
*/
function extractBrackets(string $str, string $bracket = 'all', bool $nested = false, bool $multiple = false) {
$defs = [
'angle' => ['open' => '<', 'close' => '>', 're' => '<([^>]*)>'],
'round' => ['open' => '(', 'close' => ')', 're' => '\(([^)]*)\)'],
'square' => ['open' => '[', 'close' => ']', 're' => '\[([^\]]*)\]'],
'curly' => ['open' => '{', 'close' => '}', 're' => '\{([^}]*)\}'],
];
if ($nested) {
// 遞歸模式:\((?:[^()]+|(?R))*\)
$defs = [
'angle' => ['re' => '<((?:[^<>]+|(?R))*)>'],
'round' => ['re' => '\(((?:[^()]+|(?R))*)\)'],
'square' => ['re' => '\[((?:[^\[\]]+|(?R))*)\]'],
'curly' => ['re' => '\{((?:[^{}]+|(?R))*)\}'],
];
}
$results = [];
$targets = $bracket === 'all' ? $defs : [$bracket => $defs[$bracket]];
foreach ($targets as $name => $def) {
$pattern = '/' . $def['re'] . '/';
if ($multiple) {
preg_match_all($pattern, $str, $m);
$results[$name] = $m[1] ?? [];
} else {
preg_match($pattern, $str, $m);
$results[$name] = $m[1] ?? null;
}
}
return $bracket === 'all' ? $results : ($results[$bracket] ?? null);
}
// 使用
$str = 'Hello [test {deep}]';
print_r(extractBrackets($str, 'all', true, true));
/*
Array
(
[angle] => Array ( [0] => world (nested) )
[round] => Array ( [0] => nested )
[square] => Array ( [0] => test {deep} )
[curly] => Array ( [0] => deep )
)
*/
| 陷阱 | 現象 | 解決 |
|---|---|---|
貪婪匹配 .* | /<(.*)>/ 匹配 <a><b> 時吞掉中間 >< | 用非貪婪 .*? 或排除字元類 [^>]* |
| 特殊字元未轉義 | [ ] ( ) { } . * ? 在正則有特殊含義 | 字元類外必須轉義:\[ \( \{ |
| 多行內容 | . 預設不匹配換行 | 加 s 修飾符:/<([^>]*)>/s 或 (?s) |
| 巢狀導致錯誤匹配 | 單層正則無法正確處理 (a(b)c) | 啟用遞歸 (?R) 或改用堆疊解析器 |
| 效能災難 | 長字串 + 遞歸正則 = 指數級回溯 | 限制輸入長度、避免巢狀、改用狀態機解析 |
[^>]* 最快、最安全。(?R) 方便但易超時;生產環境建議寫 狀態機解析器(單次遍歷 O(n))。DOMDocument、XMLReader、SimpleXML。$re = '/pattern/'; 重複使用,避免迴圈內重複編譯。提取括號內容看似 trivial,實則考驗正則功力。記住核心原則:排除字元類優於非貪婪、遞歸正則慎用、結構化文檔用解析器。將 extractBrackets() 納入工具庫,下次遇到類似需求秒級解決。