我将开始说,我将在下面写的并不是确切发生的事情,但为了清楚起见,我将对其进行简化。
想象一下在使用正则表达式时会发生两次计算:第一次由 PHP 完成,第二次由 PCRE 完成,就好像它们是独立的引擎一样。为了我们的倒霉,
PHP 和 PCRE 以不同的方式评估事物。
我们这里有 3 个“人”:1)用户; 2)PHP和; 3) PCRE。
用户通过编写代码与 PHP 通信,这正是您在代码编辑器中键入的内容。
PHP 然后评估此代码并将另一位信息发送到 PCRE。这条信息与您在 CODE 中键入的信息不同。
PCRE 然后评估它并向 PHP 返回一些东西,PHP 评估这个响应并向用户返回一些东西。
我将在下面的示例中更好地解释。在那里,我将使用反斜杠(“\”)来说明发生了什么。
假设 php 文件中有这段代码:
<?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 表示 TAB)。
某些字符是 PCRE 特有的(例如,.(点)匹配任何字符,s 匹配空格)。
并且某些字符对两者都是特殊的(例如,$ 对 php 是变量名称的开头,而对于 PCRE 它断言主题的结尾)。
这就是为什么您只需要转义一次换行符,例如 \n。 PHP 会将其评估为 REAL 字符 NEW LINE 并将其发送到 PCRE。
对于点,如果你想匹配那个特定的字符,你应该使用 \. 并且 PHP 不会做任何事情,因为点不是字符串中 PHP 的特殊字符。相反,它将按原样将它们发送到 PCRE。现在在 PCRE 上,它会“看到”一个点前面的反斜杠,并理解它应该匹配那个特定的字符。如果您使用双转义 \\.,第一个反斜杠将转义第二个反斜杠,从而得到相同的结果。
如果你想匹配字符串中的美元符号,那么你应该使用\\\$。在 PHP 中,第一个反斜杠将转义第二个反斜杠,留下一个反斜杠。然后第三个反斜杠将避开美元符号。最后,结果是\$。这是 PCRE 将收到的。 PCRE 将看到反斜杠并理解美元符号不是断言主题的结尾,而是文字字符。
报价
现在我们来到了引号。它们的问题在于 PHP 以不同的方式评估字符串,具体取决于用于包围它的引号。看看吧:Strings
到目前为止,我所说的所有内容都适用于双引号。
如果您在单引号中尝试这个 '\n',PHP 会将该反斜杠评估为文字。
但是,如果在正则表达式中使用它,PCRE 将按原样获取此字符串。而且由于 n 对于 PCRE 来说也是特殊的,它会将其解释为换行符,并且 BOOM,它“神奇地”匹配字符串中的换行符。
在此处检查转义序列:Escape Sequences
正如我在开头所说的,事情的范围与我在这里试图解释的不完全一样,但我真的希望它有所帮助(并且不要让它变得比现在更混乱)。