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


Replace only the last match using preg_replace()

所以,例如用户输入一些正则表达式匹配,他希望最后一个匹配将被input-string替换。

的例子:

$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!";

使用反向查找以确保只匹配最后出现的搜索字符串:

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!

我是这么想的:

短版:

它是脆弱的(例如,如果用户使用组(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]));
}
一个一般的警告:你应该非常小心用户输入,因为它可以做所有讨厌的事情。要时刻准备好接受那些相当"非生产性"的投入。
解释:match last功能的核心是?(贪婪反转)运算符(参见Repetition -在中间的某个地方)。

虽然重复模式(例如.*)默认是贪婪的,消耗尽可能多地匹配,使模式不贪婪(例如.*?)将使其匹配尽可能少(同时仍然匹配)。

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