警告:preg_replace(): 未知修饰符

2022-08-30 09:09:53

我有以下错误:

警告:preg_replace(): 第 38 行 xxx.php 中未知修饰符“]”

这是第 38 行的代码:

<?php echo str_replace("</ul></div>", "", preg_replace("<div[^>]*><ul[^>]*>", "", wp_nav_menu(array('theme_location' => 'nav', 'echo' => false)) )); ?>

如何解决此问题?


答案 1

错误发生的原因

在 PHP 中,正则表达式需要包含在一对分隔符中。分隔符可以是任何非字母数字、非反斜杠、非空格字符;、是最常用的。请注意,也可以使用括号样式分隔符,其中左括号和右括号是开始和结束分隔符,即 等都是有效的。/#~<pattern_goes_here>[pattern_goes_here]

未知修饰符 X”错误通常发生在以下两种情况下:

  • 当正则表达式缺少分隔符时

  • 当您在模式内使用分隔符而不转义它时。

在本例中,正则表达式为 。正则表达式引擎将从 到 的所有内容视为正则表达式模式,并将之后的所有内容视为修饰符。<div[^>]*><ul[^>]*><>

Regex: <div[^>  ]*><ul[^>]*>
       │     │  │          │
       └──┬──┘  └────┬─────┘
       pattern    modifiers

]这里是一个未知的修饰符,因为它出现在结束分隔符之后。这就是 PHP 抛出这个错误的原因。>

根据模式的不同,未知修饰符的抱怨也可能是关于 、 、 或几乎任何其他字母/符号。只有有效的 PCRE 修饰符*+p/)imsxeADSUXJu

如何修复它

修复很容易。只需使用任何有效的分隔符包装正则表达式模式即可。在这种情况下,您可以选择并获取以下内容:~

~<div[^>]*><ul[^>]*>~
│                   │
│                   └─ ending delimiter
└───────────────────── starting delimiter

如果在使用了分隔符后仍收到此错误,则可能是因为模式本身包含所述分隔符的未转义实例。

或转义分隔符

/foo[^/]+bar/i肯定会抛出一个错误。因此,如果它出现在正则表达式中的任何位置,则可以使用反斜杠对其进行转义:\

/foo[^\/]+bar/i
│      │     │
└──────┼─────┴─ actual delimiters
       └─────── escaped slash(/) character

如果您的正则表达式模式包含如此多的分隔符字符,这是一项繁琐的工作。

当然,更简洁的方法是使用完全不同的分隔符。理想情况下,一个不出现在正则表达式模式中的任何位置的字符,例如 - 。##foo[^/]+bar#i

更多阅读:


答案 2

其他示例

参考答案已经解释了“未知修饰符”警告的原因。这只是其他典型变体的比较。

  • 当忘记添加正则表达式分隔符时,第一个非字母符号将被假定为一个。因此,警告通常是关于分组之后的内容,元符号://(…)[…]

    preg_match("[a-zA-Z]+:\s*.$"
                ↑      ↑⬆
    
  • 有时,您的正则表达式已经使用了自定义分隔符(此处),但仍包含与未转义文本相同的字符。然后它被误认为是不成熟的分隔符。这就是为什么下一个符号收到“未知修饰❌符”奖杯的原因::

    preg_match(":\[[\d:/]+\]:"
                ↑     ⬆     ↑
    
  • 使用经典分隔符时,请注意不要将其按字面意思放在正则表达式中。在尝试匹配未转义的文件名时,最常发生这种情况:/

    preg_match("/pathname/filename/i"
                ↑        ⬆         ↑
    

    或者在匹配角度/方括号样式标签时:

    preg_match("/<%tmpl:id>(.*)</%tmpl:id>/Ui"
                ↑               ⬆         ↑
    
  • 模板样式(Smarty 或 BBCode)正则表达式模式通常需要或方括号。两者通常都应该转义。(不过,最外层的一对是例外)。{…}[…]{}

    当不使用实际的分隔符时,它们也会被误解为成对的分隔符。如果它们也被用作内部的文字字符,那么当然......错误。

    preg_match("{bold[^}]+}"
                ↑      ⬆  ↑
    
  • 每当警告说“分隔符不能是字母数字或反斜杠”时,您也完全忘记了分隔符:

    preg_match("ab?c*"
                ↑
    
  • "Unkown 修饰符 'g'“ 通常表示从 JavaScript 或 Perl 逐字复制的正则表达式。

    preg_match("/abc+/g"
                      ⬆
    

    PHP 不使用全局标志。相反,preg_replace函数适用于所有发生,preg_match_all是一次性preg_match的“全局”搜索吊坠。/g

    因此,只需删除该标志即可。/g

    另请参见:
    ·警告: preg_replace(): 未知修饰符 “g”
    ·preg_replace:错误的正则表达式==“未知修饰符”?

  • 一个更奇怪的情况是PCRE_EXTENDED /x 标志。这通常(或应该)用于使正则表达式更加崇高和可读。

    这允许使用内联注释。PHP 在 PCRE 之上实现正则表达式分隔符。但它没有以任何特殊的方式对待。这就是注释中的文字分隔符如何成为错误:###

    preg_match("/
       ab?c+  # Comment with / slash in between
    /x"
    

    (同样值得注意的是,用作分隔符可能是双重不可取的。##abc+#x

  • 将变量插值到正则表达式中需要对它们进行预转义,或者它们本身是有效的正则表达式。您无法事先判断这是否有效:

     preg_match("/id=$var;/"
                 ↑    ↺   ↑
    

    在这种情况下,最好应用 $var = preg_quote($var, “/”)。

    另请参见:
    ·未知修饰符“/”在...?它是什么?

    另一种方法是对未加引号的文字字符串使用转义:\Q…\E

     preg_match("/id=\Q{$var}\E;/mix");
    

    请注意,这只是元符号的便捷快捷方式,不可靠/安全。如果包含文字本身(尽管不太可能),它会分崩离析。它不会掩盖分隔符本身。$var'\E'

  • 不推荐使用的修饰符 /e 是一个完全不同的问题。这与分隔符无关,但隐式表达式解释模式正在逐步淘汰。另请参见:将已弃用的 preg_replace /e 替换为 preg_replace_callback

替代正则表达式分隔符

如前所述,此错误的最快解决方案只是选择一个不同的分隔符。可以使用任何非字母符号。视觉上独特的通常是首选:

  • ~abc+~
  • !abc+!
  • @abc+@
  • #abc+#
  • =abc+=
  • %abc+%

从技术上讲,您可以使用 或 分隔符。但是,最好避免使用本身用作正则表达式元字符的符号。$abc$|abc|

哈希作为分隔符也相当流行。但应注意与 / 可读性修饰符结合使用。你不能使用或注释,因为它们会被混淆为分隔符。#xPCRE_EXTENDED# inline(?#…)

仅引号分隔符

有时,您会看到并用作正则表达式分隔符,并将它们的 conterpart 作为 PHP 字符串外壳配对:"'

  preg_match("'abc+'"
  preg_match('"abc+"'

就PHP而言,这是完全有效的。它有时方便且不显眼,但在IDE和编辑器中并不总是清晰可辨。

成对分隔符

一个有趣的变体是成对的分隔符。您可以使用任何方括号/大括号组合,而不是在正则表达式的两端使用相同的符号。<...>(...)[...]{...}

  preg_match("(abc+)"   # just delimiters here, not a capture group

虽然它们中的大多数也用作正则表达式元字符,但您通常无需进一步努力即可使用它们。只要正则表达式中的那些特定大括号/帕伦正确配对或转义,这些变体就非常可读。

花哨的正则表达式分隔符

一个有点懒惰的技巧(特此不认可)是使用不可打印的ASCII字符作为分隔符。这在 PHP 中很容易工作,方法是对正则表达式字符串使用双引号,对分隔符使用八进制转义:

 preg_match("\001 abc+ \001mix"

只是一个通常不需要的控制字符。因此,它极不可能出现在大多数正则表达式模式中。这使得它适合这里,即使不是很清晰。\001

可悲的是,你不能使用Unicode glyps作为分隔符。PHP 只允许单字节字符。这是为什么呢?好吧,很高兴你问:

PCRE 顶部的 PHPs 分隔符

preg_* 函数利用 PCRE 正则表达式引擎,该引擎本身并不关心或提供分隔符。为了与Perl相似,函数实现了它们。这也是为什么您可以使用修饰符字母 /ism 而不仅仅是常量作为参数的原因。preg_*

请参阅 ext/pcre/php_pcre.c 了解如何预处理正则表达式字符串:

  • 首先,忽略所有前导空格。

  • 任何非字母数字符号都被视为假定的分隔符。请注意,PHP 仅支持单字节字符:

    delimiter = *p++;
    if (isalnum((int)*(unsigned char *)&delimiter) || delimiter == '\\') {
            php_error_docref(NULL,E_WARNING, "Delimiter must not…");
            return NULL;
    }
    
  • 正则表达式字符串的其余部分从左到右遍历。仅忽略反斜杠转义符号。\Q\E 转义不被接受。\\

  • 如果再次找到分隔符,则验证其余部分仅包含修饰符字母。

  • 如果分隔符是可配对的大括号/方括号之一,则处理逻辑更加精细。([{< )]}> )]}>

    int brackets = 1;   /* brackets nesting level */
    while (*pp != 0) {
            if (*pp == '\\' && pp[1] != 0) pp++;
            else if (*pp == end_delimiter && --brackets <= 0)
                    break;
            else if (*pp == start_delimiter)
                    brackets++;
            pp++;
    }
    

    它查找正确配对的左右分隔符,但在计数时忽略其他大括号/括号类型。

  • 只有在分隔符和修饰符标志被剪切掉之后,原始正则表达式字符串才会传递到 PCRE 后端。

现在这一切都有点无关紧要。但解释了分隔符警告的来源。这整个过程都是为了具有最低限度的Perl兼容性。当然,也有一些小的偏差,比如字符类上下文在PHP中没有得到特殊处理。[…]

更多参考资料


推荐