You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP preg_replace如何匹配第一个闭合括号无需调整替换顺序

PHP正则替换括号内容为del/ins标签的无顺序依赖方案

问题复现

初始实现代码如下,目标是将单括号包裹的内容替换为<del>标签,双括号包裹的内容替换为<ins>标签:

$text = 'This is (foo)((bar))';

$text = preg_replace('/\((.*)\)*/', '<del>$1</del>', $text);
$text = preg_replace('/\(\((.*)\)\)/', '<ins>$1</ins>', $text);

实际输出不符合预期:

This is <del>foo)((bar)</del>

问题根源是正则默认采用贪婪匹配模式,.*会尽可能匹配最长字符,导致捕获到了最后一个闭合括号,而非最近的闭合括号。
预期正确输出:

This is <del>foo</del><ins>bar</ins>

已知调换两次替换的顺序(先替换双括号ins规则,再替换单括号del规则)可以解决问题,以下是不需要调整替换顺序、逻辑更严谨的实现方案。


最优方案:单次正则分支匹配,无顺序依赖

用一次preg_replace完成两类规则的匹配,利用正则分支从左到右优先匹配的特性,把更长的双括号匹配规则写在前面,同时将贪婪匹配改为非贪婪匹配,从根源避免跨括号捕获:

$text = 'This is (foo)((bar))';

$text = preg_replace_callback(
    // 分支1优先匹配双括号包裹内容,分支2匹配单括号包裹内容,.*?为非贪婪匹配,只匹配到最近的闭合括号
    '/\(\((.*?)\)\)|\((.*?)\)/',
    function($matches) {
        // 命中双括号分支时返回ins标签,命中单括号分支时返回del标签
        if (!empty($matches[1])) {
            return "<ins>{$matches[1]}</ins>";
        }
        return "<del>{$matches[2]}</del>";
    },
    $text
);

方案优势:

  • 完全不存在替换顺序问题,仅需一次正则替换即可完成
  • 非贪婪匹配.*?只会匹配到最近的闭合括号,不会出现跨括号长捕获的问题
  • 兼容连续多个单/双括号混合的场景,例如(a)((b))(c)可以正确替换为<del>a</del><ins>b</ins><del>c</del>

备选方案:限定匹配字符范围,降低顺序敏感度

如果希望保留两次替换的写法,可以将正则中匹配任意字符的.*替换为[^()]+(仅匹配不包含括号的字符),从规则上杜绝跨括号匹配的可能,这种写法对替换顺序的敏感度极低,哪怕后续误改顺序也不会出现最初的长文本误捕获问题:

$text = 'This is (foo)((bar))';

// 注意:该写法如果严格不调整顺序,需要把双括号匹配放在前,单括号匹配放在后,但因为匹配规则限制,不会出现原问题的跨括号错误
$text = preg_replace('/\(\(([^()]+)\)\)/', '<ins>$1</ins>', $text);
$text = preg_replace('/\(([^()]+)\)/', '<del>$1</del>', $text);

方案优势:

  • 正则性能比非贪婪匹配更高,匹配逻辑更直白
  • 不会出现跨括号捕获的严重错误,即使顺序写反,也只会出现局部标签匹配偏差,不会出现大段文本被错误包裹的问题
  • 天然避免嵌套括号的误匹配

提示:如果业务场景存在合法的括号嵌套需求(例如((test (content)))这类结构),上述基础正则无法支持,需要编写递归正则处理嵌套结构,普通无嵌套的文本场景下上述方案完全可用。


内容的提问来源于stack exchange,提问作者Md. Ashraful Alam Shemul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 10:54:22