使用preg_replace()仅替换最后一个匹配项

时间:2014-05-28 16:06:36

标签: php regex

因此,例如用户输入一些正则表达式匹配,他希望最后一个匹配将被输入字符串替换。

示例:

$str = "hello, world, hello!";

// For now, regex will be for example just word, 
// but it should work with match too
replaceLastMatch($str, "hello", "replacement"); 

echo $str; // Should output "hello, world, replacement!";

2 个答案:

答案 0 :(得分:1)

使用negative lookahead确保您只匹配搜索字符串的最后一次出现:

function replaceLastMatch($str, $search, $replace) {
    $pattern = sprintf('~%s(?!.*%1$s)~', $search);
    return preg_replace($pattern, $replace, $str, 1);
}

用法:

$str = "hello, world, hello!";
echo replaceLastMatch($str, 'h\w{4}', 'replacement');
echo replaceLastMatch($str, 'hello', 'replacement');

输出:

hello, world, replacement!

Demo

答案 1 :(得分:0)

以下是我提出的建议:

简短版:

虽然它很容易受到攻击(例如,如果用户使用群组(abc),这将会破坏):

function replaceLastMatch($string, $search, $replacement) {
    // Escape all / as it delimits the regex
    // Construct the regex pattern to be ungreedy at the right (? behind .*)
    $search = '/^(.*)' . str_replace('/', '\\/', $search) . '(.*?)$/s';

    return preg_replace($search, '${1}' . $replacement . '${2}', $string);
}

更长版本(个人推荐):

此版本允许用户在不干扰此功能的情况下使用组(例如模式((ab[cC])+(XY)*){1,5}):

function replaceLastMatch($string, $search, $replacement) {
    // Escape all '/' as it delimits the regex
    // Construct the regex pattern to be ungreedy at the right (? behind .*)
    $search = '/^.*(' . str_replace('/', '\\/', $search) . ').*?$/s';

    // Match our regex and store matches including offsets
    // If regex does not match, return $string as-is
    if(1 !== preg_match($search, $string, $matches, PREG_OFFSET_CAPTURE))
        return $string;

    return substr($string, 0, $matches[1][1]) . $replacement
           . substr($string, $matches[1][1] + strlen($matches[1][0]));
}

一个一般的警告:你应该非常小心用户输入,因为它可以做所有令人讨厌的东西。始终为相当“非生产性”的输入做好准备。

<小时/> 说明:

匹配最后功能的核心是?(贪婪反转)运算符(请参阅Repetition - 中间某处)。

虽然默认情况下重复模式(例如.* greedy ,但消耗的尽可能多地匹配,制作模式 ungreedy < / em>(例如.*?)会尽可能少地匹配 (但仍然匹配)。

因此,在我们的例子中,模式的贪婪前部总是优先于非贪婪的后部,我们的自定义中间部分将匹配最后一个实例。