【问题标题】:To double escape or not to double escape in PHP PCRE functions?在 PHP PCRE 函数中双重转义还是不双重转义?
【发布时间】:2018-02-16 10:49:27
【问题描述】:

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

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

/\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

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

【问题讨论】:

  • “看起来它对引号并不重要,但对于美元符号,我认为需要双重转义。”\$ 是美元符号的转义序列(参见docs),因为在双引号字符串中,$... 被解释为变量。

标签: php regex


【解决方案1】:

双引号字符串

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

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

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

例如"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/"

【讨论】:

  • 我喜欢这个,但与其说是我如何提出这个工作问题,不如说它是关于理解所涉及的语义。
  • @SublymeRick 通过了解如何评估双引号中的反斜杠来更新我的答案。
  • @SublymeRick 解释了为什么有时需要三重转义 :)
  • 你摇滚杰克。谢谢你的时间:)
  • 加一个只为 preg_quote - 金
【解决方案2】:

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

例如:

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

【讨论】:

  • 我知道这是真的,但是如果我想在我的表达式中加上单引号怎么办? “怎么了?”
  • @SublymeRick 除了那个转义序列。
【解决方案3】:

只要你有一个无效的转义序列,PHP 实际上会将字符留在字符串中。来自documentation

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

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

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

【讨论】:

  • 哦,是的,我猜 & 不是一个特殊字符,不需要转义,我的错。
  • 无论如何都没关系,更多的是反斜杠;)它也适用于\.
【解决方案4】:

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

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

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

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

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

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

我缺少什么吗?

【讨论】:

    【解决方案5】:

    我将开始说,我将在下面写的并不是确切发生的事情,但为了清楚起见,我将对其进行简化。

    想象一下在使用正则表达式时会发生两次计算:第一次由 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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-01-19
      • 2012-03-09
      • 2023-03-06
      • 1970-01-01
      • 2013-09-01
      • 2010-11-30
      • 1970-01-01
      • 2011-09-10
      相关资源
      最近更新 更多