我遇到了以下错误:
警告: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)) )); ?>
我该如何解决这个问题?
我遇到了以下错误:
警告: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)) )); ?>
我该如何解决这个问题?
在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 Romagnoliereg
调用更新为preg_match
时,遇到了这个问题。必须引入分隔符。 - JoshP参考答案已经解释了“未知修饰符”警告的原因。这只是其他典型变量的比较。
当忘记添加正则表达式/
分隔符/
时,第一个非字母符号将被假定为分隔符。因此,警告通常是关于分组(…)
,[…]
元字符后面的内容:
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
是一个控制字符␁,通常不需要。因此,在大多数正则表达式模式中很难出现。虽然不太易读,但在这里很适合使用。❚
作为分隔符。PHP仅允许单字节字符。为什么呢?好吧,谢谢你问:preg_*
函数利用PCRE正则表达式引擎,它本身不关心也不提供定界符。为了与Perl相似,preg_*
函数实现了它们。这也是为什么您可以使用修饰符字母/ism
而不仅仅是常量作为参数的原因。首先忽略所有前导空格。
任何非字母数字符号都被视为预定分隔符。请注意,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中没有接受特殊处理。
更多参考资料:
如果你想获得一个异常(MalformedPatternException
),而不是警告或使用preg_last_error()
- 考虑使用T-Regx library:
<?php
try
{
return pattern('invalid] pattern')->match($s)->all();
}
catch (MalformedPatternException $e)
{
// your pattern was invalid
}
"/<div[^>]*><ul[^>]*>/"
的两侧添加分隔符。 - raina77ow<
>
),但它仍然似乎是通用且微不足道的。现有答案可能主要涵盖了这一点。但是第二个更广义/ CW 答案当然可以引入新的例子。(在我看来完全可以)-- 当然,RTM / 手册链接+结尾确实足以回答大多数这些问题。但我想这个问题可能会成为一个更方便的重复关闭替代方案。 - mariopcre.c
解释分隔符提取,然后再次恢复它。与此同时,投票真正属于主要答案。CW只是附录而已:] - mario