避免在替换字符串中处理特殊preg字符

时间:2010-09-24 00:42:54

标签: php regex preg-replace pcre

在PHP中使用preg_replace()和运行时生成的字符串时,可以使用preg_quote()保护搜索字符串中的特殊正则表达式字符(例如“$”或“+”)。但是在替换字符串中处理这个问题的正确方法是什么?以此代码为例:

<?php

$haystack = '...a bit of sample text...';
$replacement = '\\HELLO WORLD$1.+-';
$replacement_quoted = preg_quote($replacement);

var_dump('--replacement', $replacement, '--replacement_quoted',
    $replacement_quoted, '--haystack', $haystack);

$result1 = preg_replace("@(bit) (of) (sample)@is", "\${1}" . $replacement ."$3", $haystack);
$result2 = preg_replace("@(bit) (of) (sample)@is", "\${1}" . $replacement_quoted ."$3", $haystack);

$replacement_new1 = str_replace('$', '\$', $replacement);
$replacement_new2 = str_replace('\\', '\\\\', $replacement_new1);

$result3 = preg_replace("@(bit) (of) (sample)@is", "\${1}" . $replacement_new1 ."$3", $haystack);
$result4 = preg_replace("@(bit) (of) (sample)@is", "\${1}" . $replacement_new2 ."$3", $haystack);

var_dump('--result1 (not quoted)', $result1, '--result2 (quoted)', $result2,
    '--result3 ($ escaped)', $result3, '--result4 (\ and $ escaped)', $result3);

?>

这是输出:

string(13) "--replacement"
string(17) "\HELLO WORLD$1.+-"
string(20) "--replacement_quoted"
string(22) "\\HELLO WORLD\$1\.\+\-"
string(10) "--haystack"
string(26) "...a bit of sample text..."
string(22) "--result1 (not quoted)"
string(40) "...a bit\HELLO WORLDbit.+-sample text..."
string(18) "--result2 (quoted)"
string(42) "...a bit\HELLO WORLD$1\.\+\-sample text..."
string(21) "--result3 ($ escaped)"
string(39) "...a bit\HELLO WORLD$1.+-sample text..."
string(27) "--result4 (\ and $ escaped)"
string(39) "...a bit\HELLO WORLD$1.+-sample text..."

正如您所看到的,您无法使用preg_quote()获胜。如果你没有调用它并且只是在未修改的字符串中传递字符串(result1),那么任何看起来像捕获令牌(上面的$ 1)的东西都会被替换为 无论相应的捕获组包含什么。如果你确实调用它(result2),你对捕获组没有任何问题,但是任何其他特殊的PCRE字符(例如*)也会被转义,并且转义字符会在输出中继续存在。同样有趣的是,两个版本在输出中都产生一个\。

只有手动引用字符,特别是$,才能让它工作。这可以在result3和result4中看到。然而,继续奇数与\,结果3,它为\添加转义,而result4再次在输出中产生一个\。在替换字符串的开头添加六个\字符,在result1,result3和result4的最终输出中只产生两个\,其中三个用于result2。

因此,通过手动转义$字符似乎可以解决大多数问题。看起来像是\字符也需要被转义,但是我需要更多地考虑那个,以确切地说明什么是在讨价还价。在任何情况下,这都非常难看 - 在烦人的\ $ {1}语法和必须手动转义某些字符之间,代码只是闻起来真的很烂并且容易出错。有什么我想念的吗?有干净的方法吗?

1 个答案:

答案 0 :(得分:1)

好吧,我认为没有任何令人满意的方法可以解决这个问题。问题有两个:\字符和$字符。其他PCRE特殊字符在替换中似乎没有特殊之处。

\的情况下,事情的实际行为与人们期望的一样,你需要通过PHP \将其转义,并通过PHP定义它并将其传递给preg_replace()。在我的测试代码中,我只是将自己与两层逃逸混淆。对于$,它应该留在PHP端,并通过\进入preg_replace()进行转义。就是这样。

这里有一些代码可以证明这一切:

<?php

ini_set('display_errors', 1);
ini_set('error_reporting', E_ALL | E_STRICT);

//real string: "test1 $1 test2 \\1 test3 \${1}"

//real string manually \-escaped once for representing as a PHP string
$test = 'test1 $1 test2 \\\\1 test3 \\${1}';
var_dump('--test (starting PHP string - should match real string)', $test);

$test = str_replace(array('\\', '$'), array('\\\\', '\\$'), $test);
var_dump('--test (PHP string $-escaped and \-escaped again for preg_replace)', $test);

$result = preg_replace("/bar/", $test, 'foo bar baz');

var_dump('--result - bar should be replaced with original real string', $result);

?>

输出:

string(55) "--test (starting PHP string - should match real string)"
string(30) "test1 $1 test2 \\1 test3 \${1}"
string(66) "--test (PHP string $-escaped and \-escaped again for preg_replace)"
string(35) "test1 \$1 test2 \\\\1 test3 \\\${1}"
string(59) "--result - bar should be replaced with original real string"
string(38) "foo test1 $1 test2 \\1 test3 \${1} baz"

我的感觉是preg_quote()应该是这里的解决方案,如果preg_replace()会忽略除\本身和$以外的转义字符(例如{ {1}})。但是,它没有,迫使人们进行手动转义。事实上,我认为这是一个错误,并将继续在php.net上提交它。