首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 正則提取括號內容:尖括號、圓括號、方括號、大括號的統一解法與陷阱避坑

     編輯:PHP 相關知識

需求

從字串 "你好<我>(愛)[北京]{天安門}" 中分別提取四種括號內的內容。看似簡單,實則暗藏正則貪婪、巢狀、轉義、多行等陷阱。

基礎版:非貪婪匹配單層括號

<?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) 或改用堆疊解析器
效能災難長字串 + 遞歸正則 = 指數級回溯限制輸入長度、避免巢狀、改用狀態機解析

最佳實踐建議

  1. 簡單場景: 固定單層括號 → 排除字元類 [^>]* 最快、最安全。
  2. 巢狀需求: 遞歸正則 (?R) 方便但易超時;生產環境建議寫 狀態機解析器(單次遍歷 O(n))。
  3. HTML/XML: 絕不用正則,用 DOMDocument、XMLReader、SimpleXML。
  4. 效能敏感: 預編譯正則 $re = '/pattern/'; 重複使用,避免迴圈內重複編譯。
  5. 測試驗證: 使用 regex101 即時除錯,註解每個捕獲組用途。

總結

提取括號內容看似 trivial,實則考驗正則功力。記住核心原則:排除字元類優於非貪婪、遞歸正則慎用、結構化文檔用解析器。將 extractBrackets() 納入工具庫,下次遇到類似需求秒級解決。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved