我将开始说,我将在下面写的并不完全是发生了什么,但是为了清楚起见,我将对其进行简化。
想象一下,当使用正则表达式时发生了两个评估:第一个由 PHP 完成,第二个由 PCRE 完成,就好像它们是单独的引擎一样。为了我们的倒霉,
PHP 和 PCRE 以不同的方式评估事物。
我们这里有 3 个“人”:1)用户;2)PHP和;3)PCRE。
用户通过编写代码与 PHP 通信,这正是您在代码编辑器中键入的内容。PHP 然后评估此代码并将另一位信息发送到 PCRE。这条信息与您在 CODE 中键入的信息不同。PCRE 然后评估它并向 PHP 返回一些东西,PHP 评估这个响应并向用户返回一些东西。
我将在下面的示例中更好地解释。在那里,我将使用反斜杠(“\”)来说明正在发生的事情。
假设在 php 文件中有这段 CODE:
<?php
$sub = "A backslash \ in a string";
$pat1 = "#\#";
$pat2 = "#\\#";
$pat3 = "#\\\#";
$pat4 = "#\\\\#";
echo "sub: ".$sub;
echo "\n\n";
echo "pat1: ".$pat1;
echo "\n";
echo "pat2: ".$pat2;
echo "\n";
echo "pat3: ".$pat3;
echo "\n";
echo "pat4: ".$pat4;
?>
这将打印:
sub: A backslash \ in a string
pat1: #\#
pat2: #\#
pat3: #\\#
pat4: #\\#
在这个例子中,没有涉及到正则表达式,所以只发生了代码的 PHP 评估。
PHP 保留一个反斜杠,就好像它没有在任何特殊字符之前一样。这就是它在 $sub 中正确打印反斜杠的原因。
PHP 对 $pat1 和 $pat2 的评估完全相同,因为在 $pat1 中,反斜杠保持原样,而在 $pat2 中,第一个反斜杠转义了第二个反斜杠,从而产生了一个反斜杠。
现在,在 $pat3 中,第一个反斜杠转义了第二个反斜杠,从而产生一个反斜杠。然后 PHP 计算第三个反斜杠并保持原样,因为它没有任何特殊的前面。结果将是双反斜杠。
现在有人可能会说“但是现在我们又有两个反斜杠了!第一个不应该再次逃脱第二个吗?!” 答案是不”。在 PHP 将前两个反斜杠计算为一个后,它不再回头,而是继续评估下一个反斜杠。
此时您已经知道 $pat4 发生了什么:第一个反斜杠转义第二个反斜杠,第三个反斜杠转义第四个,最后留下两个。
现在很清楚 PHP 对这些字符串做了什么,让我们在前面的代码之后添加更多代码。
if (preg_match($pat1, $sub)) echo "test1: true"; else echo "test1: false";
echo "\n";
if (preg_match($pat2, $sub)) echo "test2: true"; else echo "test2: false";
echo "\n";
if (preg_match($pat3, $sub)) echo "test3: true"; else echo "test3: false";
echo "\n";
if (preg_match($pat4, $sub)) echo "test4: true"; else echo "test4: false";
结果是:
test1: false
test2: false
test3: true
test4: true
所以,这里发生的事情是 PHP 没有将代码中的“你输入的内容”直接发送到 PCRE。相反,PHP 发送的是它之前评估过的内容(这正是我们在上面看到的)。
对于 test1 和 test2,即使我们在 CODE 中为每个测试编写了不同的模式,PHP 仍将相同的模式#\#发送到 PCRE。test3 和 test4 发生同样的事情:PHP 正在发送#\\#。因此,test1 和 test2 以及 test3 和 test4 的结果相同。
现在,当 PCRE 评估这些模式时会发生什么?PCRE 不像 PHP。
在 test1 和 test2 中,当 PCRE 看到单个反斜杠没有转义任何特殊内容(或根本没有转义)时,它不会保持原样。相反,它可能会认为“这到底是什么?” 并向PHP返回一个错误(实际上,我真的不知道向PCRE发送单个反斜杠时发生了什么,搜索了这个,但仍然没有定论)。然后 PHP 接受我们假设的错误并将其评估为“假”并将其返回给代码的其余部分(在此示例中为if ()函数)。
在 test3 和 test4 中,事情按照我们现在的预期进行:PCRE 将第一个反斜杠评估为转义第二个反斜杠,从而产生一个反斜杠。这当然匹配 $sub 字符串并向 PHP 返回“成功消息”,PHP 将其评估为“真”。
回答问题
有些字符对于 PHP 来说是特殊的(例如, n代表新行,t代表制表符)。
某些字符对于 PCRE 是特殊的(例如,.(点)匹配任何字符,s匹配空格)。
并且某些字符对两者都是特殊的(例如, $到 php 是变量名称的开头,而对于 PCRE,它断言主题的结尾)。
这就是为什么你只需要转义一次换行符,就像这样\n。PHP 会将其评估为真实字符 NEW LINE 并将其发送到 PCRE。
对于点,如果要匹配该特定字符,则应使用\。而 PHP 什么也不做,因为点不是字符串中 PHP 的特殊字符。相反,它将按原样将它们发送到 PCRE。现在在 PCRE 上,它会“看到”一个点前面的反斜杠,并理解它应该匹配那个特定的字符。如果您使用双重转义\\. 第一个反斜杠将转义第二个反斜杠,使您得到相同的结果。
如果你想匹配字符串中的美元符号,那么你应该使用\\\$。在 PHP 中,第一个反斜杠将转义第二个反斜杠,留下一个反斜杠。然后第三个反斜杠将避开美元符号。最后,结果是\$。这是 PCRE 将收到的。PCRE 将看到反斜杠并理解美元符号不是断言主题结束,而是文字字符。
引号
现在我们来到了报价。它们的问题在于 PHP 以不同的方式评估字符串,具体取决于用于包围它的引号。看看:字符串
在此之前我所说的所有内容都适用于双引号。如果你在单引号中尝试这个'\n',PHP 会将该反斜杠作为文字来评估。
但是,如果在正则表达式中使用它,PCRE 将按原样获取此字符串。而且由于n对 PCRE 也是特殊的,它会将其解释为换行符,并且 BOOM,它“神奇地”匹配字符串中的换行符。在此处检查转义序列:转义序列
正如我在一开始所说的那样,事情并不完全像我在这里试图解释的那样,但我真的希望它有所帮助(并且不要让它比现在更混乱)。