4

我一直在寻找一篇关于何时需要双重转义以及何时不需要的可靠文章,但我找不到任何东西。也许我看起来不够努力,因为我确信在某个地方有一个解释,但是让下一个有这个问题的人很容易找到!

以以下正则表达式模式为例:

/\n/
/domain\.com/
/myfeet \$ your feet/

没有什么突破性的吧?好的,让我们在 PHP 的 preg_match 函数的上下文中使用这些示例:

$foo = preg_match("/\n/", $bar);
$foo = preg_match("/domain\.com/", $bar);
$foo = preg_match("/myfeet \$ your feet/", $bar);

据我了解,带引号的字符串值上下文中的反斜杠会转义以下字符,并且表达式是通过带引号的字符串值给出的。

以前会不会像做以下那样,这不会导致错误吗?:

$foo = preg_match("/n/", $bar);
$foo = preg_match("/domain.com/", $bar);
$foo = preg_match("/myfeet $ your feet/", $bar);

这不是我想要的吗?这些表达方式与上述不同。

我不必像这样写它们双重转义吗?

$foo = preg_match("/\\n/", $bar);
$foo = preg_match("/domain\\.com/", $bar);
$foo = preg_match("/myfeet \\$ your feet/", $bar);

这样当 PHP 处理字符串时,它会将反斜杠转义为反斜杠,然后在传递给 PCRE 解释器时保留该反斜杠?

还是 PHP 只是神奇地知道我想将该反斜杠传递给 PCRE 解释器......我的意思是它怎么知道我没有试图\"逃避我想在我的表达式中使用的引号?还是在使用转义引号时只需要双斜杠?就此而言,您是否需要三倍转义报价?\\\"你知道,所以引号被转义并留下一个双精度?

这有什么经验法则?

我刚刚用PHP做了一个测试:

$bar = "asdfasdf a\"ONE\"sfda dsf adsf me & mine adsf asdf asfd ";

echo preg_match("/me \$ mine/", $bar);
echo "<br /><br />";
echo preg_match("/me \\$ mine/", $bar);
echo "<br /><br />";
echo preg_match("/a\"ONE\"/", $bar);
echo "<br /><br />";
echo preg_match("/a\\\"ONE\\\"/", $bar);
echo "<br /><br />";

输出:

0

1

1

1

所以,看起来对引号并不重要,但对于美元符号,我认为需要双重转义。

4

5 回答 5

8

双引号字符串

当涉及在双引号内转义时,规则是 PHP 将检查紧跟在反斜杠后面的字符。

如果相邻字符在集合中,ntrvef\$"或者后面有一个数值(规则可以在此处找到),则它分别被评估为相应的控制字符或序数(十六进制或八进制)表示。

重要的是要注意,如果给出了无效的转义序列,则不会计算表达式并且反斜杠和字符都保留。这与其他一些语言不同,其中无效的转义序列会导致错误。

例如"domain\.com",将保持原样。

请注意,变量也会在双引号内展开,例如"$var"需要转义为"\$var".

单引号字符串

自 PHP 5.1.1 起,单引号字符串中的任何反斜杠(并且后跟至少一个字符)都将按原样打印,并且也不会替换任何变量。这是迄今为止单引号字符串最方便的特性。

常用表达

对于转义正则表达式,最好将转义保留为preg_quote()

$foo = preg_match('/' . preg_quote('mine & yours', '/') . '/', $bar);

这样您就不必担心需要转义哪些字符,因此它适用于用户输入。

也可以看看:preg_quote

更新

你添加了这个测试:

"/me \$ mine/"

这被评估为"/me $ mine/"; 但在 PCRE 中$具有特殊含义(它是主题结束锚)。

"/me \\$ mine/"

这被评估为"/me \$ mine/",因此反斜杠对 PHP 本身进行了$转义,而对 PCRE 进行了转义。顺便说一句,这只是偶然的。

$var = 'something';

"/me \\$var mine/"

这被评估为"/me \something",因此您需要$再次转义。

"/me \\\$var mine/"
于 2013-02-09T00:10:09.760 回答
1

使用单引号。它们防止发生转义序列。

例如:

php > print "hi\n";
hi
php > print 'hi\n';
hi\nphp > 
于 2013-02-09T00:09:55.097 回答
0

好的所以我做了一些更多的测试并发现了在双引号中封装 PCRE 时的拇指规则,以下是正确的:

$- 需要双重转义,因为如果文本紧随其后,PHP 会将其解释为变量的开头。未转义,它将指示您的针头结束并会折断。

\r\n\t\v- 特殊的 PHP 字符串转义,仅需要单个转义。

[\^$.|?*+()- 特殊的正则表达式字符,只需要单转义。不必要地使用时,双重转义似乎不会破坏表达式。

"- 由于封装,引号显然必须转义,但只需要转义一次。

\- 寻找反斜杠?使用表达式的双引号封装,这将需要 3 次转义!\\(总共四个反斜杠)

有什么我想念的吗?

于 2013-02-09T00:47:27.057 回答
0

每当您有一个无效的转义序列时,PHP 实际上将字符留在字符串中。从文档中:

与单引号字符串一样,转义任何其他字符也会导致打印反斜杠。

"\&"真的被解释为"\&"。转义序列并不多,因此在大多数情况下,您可能只需要一个反斜杠即可。但为了保持一致性,转义反斜杠可能是更好的选择。

一如既往:知道你在做什么:)

于 2013-02-09T00:16:14.223 回答
0

我将开始说,我将在下面写的并不完全是发生了什么,但是为了清楚起见,我将对其进行简化。

想象一下,当使用正则表达式时发生了两个评估:第一个由 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,它“神奇地”匹配字符串中的换行符。在此处检查转义序列:转义序列

正如我在一开始所说的那样,事情并不完全像我在这里试图解释的那样,但我真的希望它有所帮助(并且不要让它比现在更混乱)。

于 2018-02-16T02:49:27.010 回答