2010-09-24 178 views
4

在運行時使用在字符串中生成的字符串在PHP中使用preg_replace()時,可以使用preg_quote()來保護搜索字符串中的特殊正則表達式字符(例如'$'或'+' 。但是,在替換字符串中處理這個問題的正確方法是什麼?拿這個代碼,例如:避免處理替換字符串中的特殊preg字符

<?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中看到。但是,繼續使用\的奇怪現象,result3和\ result4會在輸出中再次產生一個\。在替換字符串的開始處添加六個\字符只會在result1,result3和result4的最終輸出中產生兩個\,並且result2中會有三個字符。

所以,似乎大多數問題都通過手動轉義$字符來處理。看起來\角色也需要逃脫,但我需要更多地思考那個角色,以確定什麼是攻擊。在任何情況下,這都非常難看 - 在令人討厭的\ $ {1}語法和不得不手動轉義某些字符之間,代碼只是聞起來很爛並且容易出錯。有什麼我失蹤?有沒有一種乾淨的方式來做到這一點?

回答

1

好吧,好吧,我不認爲有任何真正令人滿意的方式來處理這個問題。問題有兩個:\字符和$字符。其他PCRE特殊字符在替換中似乎並不特殊。

\的情況下,事情實際上表現如您所期望的那樣,您需要通過\將其轉義,並通過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()會忽略轉義字符等比\本身和$(例如,+)。然而,它並沒有強迫人們進行手動轉義。事實上,我認爲這是一個錯誤,並會在php.net上繼續提交。

+0

我提交了一個錯誤 - [(#52962)](http://bugs.php.net/bug.php?id=52962)。 – 2010-10-01 01:01:53