要在PHP PCRE函数中双重转义还是不双重转义?

时间:2013-02-09 00:07:18

标签: php regex

我正在寻找一篇关于什么时候需要双重逃逸以及什么时候没有,但我找不到任何东西的实用文章。也许我看起来不够努力,因为我确定某处有一个解释,但是让我们很容易找到有这个问题的下一个人!

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

/\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解释器...我的意思是它怎么知道我不是想\"逃避我想在我的用法中使用的引用表达?或者只是在使用转义报价时需要双斜线?就此而言,你需要TRIPLE逃避报价吗? \\\"你知道吗,所以报价被转义并留下了双倍的结果?

这与经验法则有什么关系?

我刚用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

所以,它似乎不知何故它对引号并不重要,但对于美元符号,我认为需要双重逃避。

5 个答案:

答案 0 :(得分:8)

双引号字符串

当涉及到双引号内部转义时,规则是PHP将在反斜杠后立即检查字符。

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

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

E.g。 "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/"

答案 1 :(得分:1)

使用单引号。它们阻止了逃逸序列的发生。

例如:

php > print "hi\n";
hi
php > print 'hi\n';
hi\nphp > 

答案 2 :(得分:0)

每当你有一个无效的转义序列时,PHP实际上将字符保留在字符串中。来自documentation

  

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

即。 "\&"实际上被解释为"\&"。没有那么多的转义序列,因此在大多数情况下,你可能会使用一个反斜杠。但为了保持一致性,逃避反斜杠可能是更好的选择。

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

答案 3 :(得分:0)

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

$ - 需要双重转义,因为如果文本紧跟在它之后,PHP会将其解释为变量的开头。保持未转义状态,它将指示针的末端并将断裂。

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

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

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

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

我缺少什么?

答案 4 :(得分:0)

我开始说我下面写的所有内容并不完全是这样,但为了清楚起见,我会简化它。

想象一下使用正则表达式时会发生两次评估:第一次由PHP完成,第二次由PCRE完成,就像它们是单独的引擎一样。而对于我们运气不好,

PHP和PCRE以不同的方式评估这些内容。

我们有3个&#34;家伙&#34;这里:1)用户; 2)PHP和; 3)PCRE。

USER通过编写CODE与PHP进行通信,这正是您在代码编辑器中输入的内容。 PHP然后评估此CODE并向PCRE发送另一部分信息。这些信息与您在CODE中输入的信息不同。 PCRE然后对其进行评估并向PHP返回一些内容,评估此响应并向USER返回一些内容。

我将在下面的例子中更好地解释。在那里,我将使用反斜杠(&#34; \&#34;)来说明正在发生的事情。

在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会评估第三个反斜杠并保持原样,因为它不会出现任何特殊情况。结果将是双反斜杠。

现在有人可以说&#34;但现在我们又有两个反斜杠了!不应该让第一个人再次逃离第二个人吗?!&#34; 答案是&#34;否&#34;。在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没有发送&#34;你键入的内容&#34;在CODE中直接到PCRE。相反,PHP正在发送它之前评估过的内容(这正是我们上面看到的)。

对于test1和test2,即使我们在CODE中为每个测试编写了不同的模式,PHP也会向PCRE发送相同的模式#\#。 test3和test4也是如此:PHP正在发送#\\#。因此,test1和test2的结果以及test3和test4的结果相同。

现在,当PCRE评估这些模式时会发生什么? PCRE并不像PHP一样。

在test1和test2中,当PCRE看到单个反斜杠没有任何特殊情况(或者根本没有)时,它不会保持原样。相反,它可能会认为&#34;这到底是什么?&#34;并向PHP返回一个错误(实际上,我不知道在向PCRE发送单个反斜杠时发生了什么,搜索了这个,但仍然没有定论)。然后PHP采用我们假设的错误并将其评估为&#34; false&#34;并将其返回到CODE的其余部分(在此示例中, if()函数)。

在test3和test4中,事情就像我们现在所期望的那样:PCRE将第一个反斜杠计算为逃避第二个反斜杠,从而产生一个反斜杠。那当然匹配$ sub字符串并返回一条成功的消息&#34;到PHP,它将其评估为&#34; true&#34;。

回答问题
有些字符对PHP来说很特殊(例如,NEW LINE的 n ,TAB的 t )。
有些字符对于PCRE是特殊的(例如(点)匹配任何字符, s 匹配空格)。
并且一些字符对两者都是特殊的(例如, $ 到php是变量名称的开头,而PCRE则断言主题的结尾)。

这就是为什么你需要一次转义换行符,比如 \ n 。 PHP会将其评估为真实角色NEW LINE并将其发送给PCRE。

对于点,如果要匹配该特定字符,则应使用 \。,PHP将不执行任何操作,因为点不是PHP中的特殊字符即可。相反,它会将它们按原样发送给PCRE。现在在PCRE上,它会&#34;看到&#34;点之前的反斜杠,并了解它应该与该特定字符匹配。如果你使用双重转义 \\。,第一个反斜杠将逃脱第二个反转,留下相同的结果。

如果您想匹配字符串中的美元符号,则应使用 \\\ $ 。在PHP中,第一个反斜杠将逃脱第二个反斜杠,留下一个反斜杠。然后第三个反斜杠将逃脱美元符号。最后,结果是 \ $ 。这就是PCRE将收到的。 PCRE将看到反斜杠,并理解美元符号不是断言主题的结尾,而是文字字符。

<强> QUOTES

现在我们来引用了。它们的问题在于PHP以不同的方式评估字符串,具体取决于用于包围它的引号。看看:Strings

所有我说过,直到这一点对双引号有效。 如果您在单引号中尝试&#39; \ n&#39; ,PHP会将该反斜杠评估为字面值。
但是,如果它在正则表达式中使用,PCRE将按原样获取此字符串。而且,因为 n 对PCRE来说也是特殊的,它会将其解释为换行符,而BOOM则是&#34; magicaly&#34;匹配字符串中的换行符。 在此处检查转义序列:Escape Sequences

正如我在开始时所说的,事情的区域并不像我在这里解释的那样,但我真的希望它有所帮助(而不是让它比现在更加混乱)。