警告:preg_replace():未知修改器。

72

我遇到了以下错误:

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

这是第38行的代码:

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

我该如何解决这个问题?


12
在模式 "/<div[^>]*><ul[^>]*>/" 的两侧添加分隔符。 - raina77ow
2
@mario,我真的不明白你为什么要在这里设置赏金?你真的在寻找新的答案吗?如果是的话,那么现有的答案有什么问题吗? - Rizier123
3
是的,这并不是为了吸引更多答案。现有的答案已经是一个相当出色的例子了。这是一个非常好的视觉解释,并且可能适用于许多类似的情况。而这样的小赏金主要是作为临时的**公共书签来使用-以使其更加知名。也许可以在之后创建一个人工的CW答案,包含额外的例子和链接…… - mario
1
@hek2mgl 好观点。我没有真正考虑到 HTML 的特殊性。虽然这使它有点特殊(相当于意外使用了一对可能的分隔符 < >),但它仍然似乎是通用且微不足道的。现有答案可能主要涵盖了这一点。但是第二个更广义/ CW 答案当然可以引入新的例子。(在我看来完全可以)-- 当然,RTM / 手册链接+结尾确实足以回答大多数这些问题。但我想这个问题可能会成为一个更方便的重复关闭替代方案。 - mario
2
@Rizier123 我会在7.5小时内勾选它。赏金奖励有效期为24小时。根据我的经验,你最好在赏金期结束时获得最多的浏览量。可能不会吸引新的答案(不需要;但我不想阻止任何人)。这也是为什么我将CW答案保持在“潜入模式”中的原因。接下来,我会用pcre.c解释分隔符提取,然后再次恢复它。与此同时,投票真正属于主要答案。CW只是附录而已:] - mario
显示剩余11条评论
3个回答

134

为什么会发生错误

在PHP中,正则表达式需要用一对定界符来括起来。定界符可以是任何非字母数字、非反斜线、非空格的字符;常用的有 /#~。请注意,也可以使用方括号样式的定界符,其中开头和结尾括号是起始和结束定界符,即<pattern_goes_here>[pattern_goes_here]等都是有效的。

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

  • 当您的正则表达式缺少定界符时。

  • 当您在模式中没有转义定界符的情况下使用定界符

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

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

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

根据模式,未知的修饰符投诉可能也会涉及到 *+p/) 或几乎任何其他字母/符号。只有 imsxeADSUXJu有效的 PCRE 修饰符

如何解决

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

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

如果您已经使用分隔符,但仍然收到此错误消息,可能是因为模式本身包含未转义的该分隔符出现。

或者转义分隔符

/foo[^/]+bar/i肯定会引发错误。所以,如果它在正则表达式中出现,您可以使用\反斜杠进行转义:

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

如果你的正则表达式模式中包含很多分隔符字符,那么这将是一项繁琐的工作。

当然,更好的方法是使用完全不出现在正则表达式模式中的不同分隔符。比如说,用 # 作为分隔符:#foo[^/]+bar#i

更多阅读:


我注意到当分隔符之一在 preg_quote() 中时,会发生相同的情况,因此像 preg_replace('/'.preg_quote('/').'/i','',$string); 这样的东西会给出与主题相同的错误。斜杠不应该被 preg_quote() 转义吗? - Niki Romagnoli
当我将一些旧的ereg调用更新为preg_match时,遇到了这个问题。必须引入分隔符。 - JoshP

19

其他示例

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

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

  • 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*"
                ↑
    
  • "未知修饰符 'g'" 经常表示正则表达式是直接从 JavaScript 或 Perl 中复制而来。

  • preg_match("/abc+/g"
                      ⬆
    

    PHP不使用全局标志/g,而是preg_replace函数可以处理所有出现的情况,preg_match_all是与单次匹配的preg_match相对应的全局搜索。

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

    另请参阅:
    · Warning: preg_replace(): Unknown modifier 'g'
    · preg_replace: bad regex == 'Unknown Modifier'?

  • 更特殊的情况是关于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转义:

    另请参阅:
    · Unknown modifier '/' in ...? what is it?

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

    请注意,这仅是元符号的方便快捷方式,并不可靠/安全。如果$var本身包含了字面值'\E'(虽然很少见),它将会失效。而且它不能遮蔽分隔符本身。

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

其他正则表达式分隔符

如前所述,此错误的最快解决方法就是选择一个不同的分隔符。任何非字母符号都可以使用。通常更喜欢使用视觉上有区别的符号:

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

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

哈希#作为分隔符也相当受欢迎。但是,在与x/PCRE_EXTENDED可读性修饰符结合使用时需要小心。然后您不能使用# inline(?#…) 注释,因为它们将被混淆为分隔符。

仅引用的定界符

偶尔你会看到"' 与其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字形作为分隔符。PHP仅允许单字节字符。为什么呢?好吧,谢谢你问:
PHP的定界符位于PCRE之上 preg_*函数利用PCRE正则表达式引擎,它本身不关心也不提供定界符。为了与Perl相似,preg_*函数实现了它们。这也是为什么您可以使用修饰符字母/ism而不仅仅是常量作为参数的原因。
请参见ext/pcre/php_pcre.c以了解如何预处理正则表达式字符串:

1
非常好的解释。 - Svetoslav Marinov
非常有帮助!谢谢。我真的很难理解一些句法背后的原因和目的。 - undefined

1

如果你想获得一个异常(MalformedPatternException),而不是警告或使用preg_last_error() - 考虑使用T-Regx library:

<?php
try 
{
    return pattern('invalid] pattern')->match($s)->all();
}
catch (MalformedPatternException $e) 
{
    // your pattern was invalid
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接