使用 str_replace 使其仅作用于第一个匹配项?

Posted

技术标签:

【中文标题】使用 str_replace 使其仅作用于第一个匹配项?【英文标题】:Using str_replace so that it only acts on the first match? 【发布时间】:2010-11-18 04:05:42 【问题描述】:

我想要一个版本的str_replace(),它只替换$subject 中第一次出现的$search。是否有一个简单的解决方案,或者我需要一个 hacky 解决方案?

【问题讨论】:

您可能会发现s($subject)->replaceFirst($search)s($subject)->replaceFirstIgnoreCase($search) 很有帮助,如this standalone library 中所示。 您可能想知道 $count 参数是什么,它是 OUTPUT - 执行了多少替换 - 不是限制,所以这是一个完全有效的问题。这可能会与其他语言混淆,因为例如Python 的 str.replace 确实使用计数参数作为限制参数。 好问题,赞成!只是一个想法,也许重新考虑一个公认的答案?干杯! 【参考方案1】:

没有它的版本,但解决方案一点也不 hacky。

$pos = strpos($haystack, $needle);
if ($pos !== false) 
    $newstring = substr_replace($haystack, $replace, $pos, strlen($needle));

非常简单,并且节省了正则表达式的性能损失。

奖励:如果您想替换 last 次出现,只需使用 strrpos 代替 strpos

【讨论】:

可以比正则表达式更快,并且使用更少的内存。不知道为什么有人会投票反对...... 我喜欢这种方法,但是代码有错误,substr_replace调用的最后一个参数应该是strlen($needle)而不是strlen($replace)..请注意!!跨度> 它是“hacky”,因为它需要更多的时间来弄清楚发生了什么。此外,如果它是清晰的代码,就不会提到代码有错误。如果这么小的sn-p有可能出错,那已经太hacky了。 我不同意@CamiloMartin 关于行数与错误可能性的观点。虽然 substr_replace 由于所有参数而使用起来有些笨拙,但真正的问题是通过数字进行字符串操作有时只是 棘手 - 你必须小心传递正确的变量/偏移功能。实际上,我什至可以说上面的代码是最直接的,对我来说也是最合乎逻辑的方法。 绝妙的方法。在替换其中保留正则表达式字符的变量值时完美工作(所以 preg_replace 是熊)。这简单而优雅。【参考方案2】:

可以使用preg_replace:

function str_replace_first($search, $replace, $subject)

    $search = '/'.preg_quote($search, '/').'/';
    return preg_replace($search, $replace, $subject, 1);


echo str_replace_first('abc', '123', 'abcdef abcdef abcdef'); 
// outputs '123def abcdef abcdef'

神奇之处在于可选的第四个参数 [Limit]。来自文档:

[限制] - 可能的最大值 替换每个模式中的每个 主题字符串。默认为 -1(无 限制)。

不过,请参阅 zombat's answer 了解更有效的方法(大约快 3-4 倍)。

【讨论】:

这种方法的缺点是正则表达式的性能损失。 另一个缺点是您必须在“needle”上使用 preg_quote() 并在替换中转义元字符 $ 和 \。 由于令人讨厌的转义问题,这作为通用解决方案失败了。 由于“性能”,正则表达式经常被忽略,如果性能是首要考虑因素,我们就不会编写 php! '/' 以外的东西可以用来包装模式,也许是 '~',这将有助于在一定程度上避免转义问题。这取决于数据是什么,以及它来自哪里。 性能方面的缺点不谈 - 除了preg_quote 中的潜在错误之外,那些抱怨逃避问题的人是否有任何具体的想法?例如,@ThomasRedstone 担心分隔符 / 如果出现在 $from 中可能会很危险,但幸运的是它不是:由于preg_quote 的第二个参数,它被正确转义(可以轻松测试) .我很想知道具体问题(在我的书中这将是严重的 PCRE 安全漏洞)。【参考方案3】:

编辑:两个答案都已更新,现在都是正确的。我会留下答案,因为功能计时仍然有用。

不幸的是,'zombat' 和'too much php' 的答案是不正确的。这是对发布的答案 zombat 的修订(因为我没有足够的声誉来发表评论):

$pos = strpos($haystack,$needle);
if ($pos !== false) 
    $newstring = substr_replace($haystack,$replace,$pos,strlen($needle));

注意 strlen($needle),而不是 strlen($replace)。只有当 needle 和 replace 长度相同时,Zombat 的示例才能正常工作。

这是一个函数中的相同功能,其签名与 PHP 自己的 str_replace 相同:

function str_replace_first($search, $replace, $subject) 
    $pos = strpos($subject, $search);
    if ($pos !== false) 
        return substr_replace($subject, $replace, $pos, strlen($search));
    
    return $subject;

这是'too much php'的修改答案:

implode($replace, explode($search, $subject, 2));

注意末尾的 2 而不是 1。或者在函数格式中:

function str_replace_first($search, $replace, $subject) 
    return implode($replace, explode($search, $subject, 2));

我对这两个函数进行了计时,当没有找到匹配项时,第一个函数的速度是前者的两倍。找到匹配项时,它们的速度相同。

【讨论】:

为什么不将其泛化为: str_replace_flexible(mixed $s, mixed $r, int $offset, int $limit) 其中函数替换 $limit 从 $offset (nth) 匹配开始。 太糟糕了,这仅适用于区分大小写的替换。 @Andrew stripos() 救援 :-)【参考方案4】:

我想知道哪个是最快的,所以我都测试了。

您会在下面找到:

已贡献到此页面的所有功能的完整列表 每个贡献的基准测试(平均执行时间超过 10,000 次) 每个答案的链接(完整代码)

所有功能都使用相同的设置进行了测试:

$string = 'OOO.OOO.OOO.S';
$search = 'OOO'; 
$replace = 'B';

仅替换字符串中第一次出现的字符串的函数:

substr_replace($string, $replace, 0, strlen($search));

[CONTRIBUTED BY] => zombat
[OOO.OOO.OOO.S] => B.OOO.OOO.S
[AVERAGE TIME] => 0.0000062883
[SLOWER BY] => FASTEST

replace_first($search, $replace, $string);

[CONTRIBUTED BY] => too much php
[OOO.OOO.OOO.S] => B.OOO.OOO.S
[AVERAGE TIME] => 0.0000073902
[SLOWER BY] => 17.52%

preg_replace($search, $replace, $string, 1);

[CONTRIBUTED BY] => karim79
[OOO.OOO.OOO.S] => B.OOO.OOO.S
[AVERAGE TIME] => 0.0000077519
[SLOWER BY] => 23.27%

str_replace_once($search, $replace, $string);

[CONTRIBUTED BY] => happyhardik
[OOO.OOO.OOO.S] => B.OOO.OOO.S
[AVERAGE TIME] => 0.0000082286
[SLOWER BY] => 30.86%

str_replace_limit($search, $replace, $string, $count, 1);

[CONTRIBUTED BY] => bfrohs - expanded renocor
[OOO.OOO.OOO.S] => B.OOO.OOO.S
[AVERAGE TIME] => 0.0000083342
[SLOWER BY] => 32.54%

str_replace_limit($search, $replace, $string, 1);

[CONTRIBUTED BY] => renocor
[OOO.OOO.OOO.S] => B.OOO.OOO.S
[AVERAGE TIME] => 0.0000093116
[SLOWER BY] => 48.08%

str_replace_limit($string, $search, $replace, 1, 0);

[CONTRIBUTED BY] => jayoaK
[OOO.OOO.OOO.S] => B.OOO.OOO.S
[AVERAGE TIME] => 0.0000093862
[SLOWER BY] => 49.26%

仅替换字符串中最后一次出现的字符串的函数:

substr_replace($string, $replace, strrpos($string, $search), strlen($search));

[CONTRIBUTED BY] => oLinkSoftware - modified zombat
[OOO.OOO.OOO.S] => OOO.OOO.B.S
[AVERAGE TIME] => 0.0000068083
[SLOWER BY] => FASTEST

strrev(implode(strrev($replace), explode(strrev($search), strrev($string), 2)));

[CONTRIBUTED BY] => oLinkSoftware
[OOO.OOO.OOO.S] => OOO.OOO.B.S
[AVERAGE TIME] => 0.0000084460
[SLOWER BY] => 24.05%

【讨论】:

感谢您,我通常使用 preg_replace,因为如果在大多数情况下需要将来进行调整,它是最灵活的,27% 的速度不会显着 @oLinkWebDevelopment 我有兴趣查看您的基准测试脚本。我认为它可能会被证明是有用的。 substr_replace() 获胜的原因很简单;因为它是一个内部函数。两个做同样事情的内部函数和用户定义函数在性能上有所不同,因为内部函数在较低层中运行。那么,为什么不preg_match()?正则表达式几乎比每个内部字符串操作函数都慢,因为它们在字符串中多次搜索。 我希望你的“赢家”(substr_replace($string, $replace, 0, strlen($search));)的基准不仅仅是写静态的0。非正则表达式解决方案的部分卷积是它们需要在知道替换位置之前“找到”起点。【参考方案5】:

不幸的是,我不知道有任何 PHP 函数可以做到这一点。 你可以像这样轻松地滚动自己的:

function replace_first($find, $replace, $subject) 
    // stolen from the comments at PHP.net/str_replace
    // Splits $subject into an array of 2 items by $find,
    // and then joins the array with $replace
    return implode($replace, explode($find, $subject, 2));

【讨论】:

我认为这是所有golfiest version - 使用join 而不是implode return implode($replace, explode($find, $subject, $limit+1)); 用于自定义替换号码【参考方案6】:

我创建了这个 little 函数,它用限制替换字符串上的字符串(区分大小写),而不需要正则表达式。它工作正常。

function str_replace_limit($search, $replace, $string, $limit = 1) 
    $pos = strpos($string, $search);

    if ($pos === false) 
        return $string;
    

    $searchLen = strlen($search);

    for ($i = 0; $i < $limit; $i++) 
        $string = substr_replace($string, $replace, $pos, $searchLen);

        $pos = strpos($string, $search);

        if ($pos === false) 
            break;
        
    

    return $string;

示例用法:

$search  = 'foo';
$replace = 'bar';
$string  = 'foo wizard makes foo brew for evil foo and jack';
$limit   = 2;

$replaced = str_replace_limit($search, $replace, $string, $limit);

echo $replaced;
// bar wizard makes bar brew for evil foo and jack

【讨论】:

虽然我宁愿使用===false 而不是is_bool( 更明确 - 我竖起大拇指只是因为它避免了正则表达式的疯狂! ...同时它正在工作和 clean 解决方案... 更喜欢易于定制的preg_ 解决方案并不是疯狂,而是个人喜好。 return preg_replace('/'.preg_quote($search, '/').'/', $replace, $content, 1); 对于不害怕正则表达式的人来说非常容易阅读。需要不区分大小写的搜索?在结束模式分隔符后添加i。需要 Unicode/多字节支持?在结束模式分隔符后添加u。需要单词边界支持?在搜索字符串的两侧添加\b。如果您不想要正则表达式,请不要使用正则表达式。课程的马,但肯定不是疯狂。【参考方案7】:

=> 代码已修订,因此认为某些 cmets 太旧了

并且感谢大家帮助我改进这一点

有什么BUG,请私信我; 我会尽快解决这个问题

那么,我们开始吧:

将第一个 'o' 替换为 'ea' 例如:

$s='I love you';
$s=str_replace_first('o','ea',$s);
echo $s;

//output: I leave you

功能:

function str_replace_first($this,$that,$s)
         
         $w=strpos($s,$this);
         if($w===false)return $s;
         return substr($s,0,$w).$that.substr($s,$w+strlen($this));
         

【讨论】:

如果 $this 有重复字符如 aaa vs aaaaaaaaa 则失败 我认为应该是substr($where,$b+strlen($this)),而不是substr($where,$b+1)。而且我猜substr_replace 更快。 代码已修改,现在它甚至适用于长字符串 此解决方案无法按编码工作。证明:3v4l.org/cMeZj 当你修复变量命名问题时,当没有找到搜索值时它不起作用——它会损坏输入字符串。证明:3v4l.org/XHtfc 有人要求修复代码公平吗? @mickmackusa 你能再检查一遍吗?【参考方案8】:
$str = "/property/details&id=202&test=123#tab-6p";
$position = strpos($str,"&");
echo substr_replace($str,"?",$position,1);

使用 substr_replace 我们可以只替换字符串中出现的第一个字符。 因为 & 重复了多次,但只有在第一个位置,我们必须用 ? 替换 & ?

【讨论】:

【参考方案9】:

最简单的方法是使用正则表达式。

另一种方法是先用strpos()然后再用substr_replace()找到字符串的位置

但我真的会选择 RegExp。

【讨论】:

与此页面上的其他帖子相比,此“提示”相当模糊/低价值。【参考方案10】:
function str_replace_once($search, $replace, $subject) 
    $pos = strpos($subject, $search);
    if ($pos === false) 
        return $subject;
    

    return substr($subject, 0, $pos) . $replace . substr($subject, $pos + strlen($search));

【讨论】:

纯代码答案在 *** 上的价值很低,因为它们在教育/授权成千上万的未来研究人员方面做得很差。【参考方案11】:
$string = 'this is my world, not my world';
$find = 'world';
$replace = 'farm';
$result = preg_replace("/$find/",$replace,$string,1);
echo $result;

【讨论】:

这和第一个答案一样。此外,在将其用作表达式之前,您应该对$find 进行preg_quote 这是我使用的,所以我投了赞成票。第一个答案引起了与 Drupal 的冲突,它必须覆盖了一个 drupal 辅助函数。所以我只取了函数内部的代码并将其与其余代码一起使用... 这个仅代码的答案在页面上提供了多余的建议(更不用说它缺少preg_quote()。这个迟到的重复答案可以安全地从页面中清除,因为它的建议是由较早的提供的,和更高票数的接受答案。【参考方案12】:

为了扩展@renocor's answer,我编写了一个与str_replace() 100% 向后兼容的函数。也就是说,您可以用str_replace_limit() 替换所有 次出现的str_replace(),而不会搞砸任何事情,即使是那些使用$search$replace 和/或$subject 的数组。

函数可以完全独立,如果你想用($string===strval(intval(strval($string))))替换函数调用,但我建议不要这样做,因为valid_integer()在处理时是一个相当有用的函数整数作为字符串提供。

注意:只要有可能,str_replace_limit() 将使用 str_replace(),因此所有对 str_replace() 的调用都可以替换为 str_replace_limit(),而无需担心达到性能。

用法

<?php
$search = 'a';
$replace = 'b';
$subject = 'abcabc';
$limit = -1; // No limit
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;

2 个替换 -- bbcbbc

$limit = 1; // Limit of 1
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;

1 个替换 -- bbcabc

$limit = 10; // Limit of 10
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;

2 个替换 -- bbcbbc

功能

<?php

/**
 * Checks if $string is a valid integer. Integers provided as strings (e.g. '2' vs 2)
 * are also supported.
 * @param mixed $string
 * @return bool Returns boolean TRUE if string is a valid integer, or FALSE if it is not 
 */
function valid_integer($string)
    // 1. Cast as string (in case integer is provided)
    // 1. Convert the string to an integer and back to a string
    // 2. Check if identical (note: 'identical', NOT just 'equal')
    // Note: TRUE, FALSE, and NULL $string values all return FALSE
    $string = strval($string);
    return ($string===strval(intval($string)));


/**
 * Replace $limit occurences of the search string with the replacement string
 * @param mixed $search The value being searched for, otherwise known as the needle. An
 * array may be used to designate multiple needles.
 * @param mixed $replace The replacement value that replaces found search values. An
 * array may be used to designate multiple replacements.
 * @param mixed $subject The string or array being searched and replaced on, otherwise
 * known as the haystack. If subject is an array, then the search and replace is
 * performed with every entry of subject, and the return value is an array as well. 
 * @param string $count If passed, this will be set to the number of replacements
 * performed.
 * @param int $limit The maximum possible replacements for each pattern in each subject
 * string. Defaults to -1 (no limit).
 * @return string This function returns a string with the replaced values.
 */
function str_replace_limit(
        $search,
        $replace,
        $subject,
        &$count,
        $limit = -1
    )

    // Set some defaults
    $count = 0;

    // Invalid $limit provided. Throw a warning.
    if(!valid_integer($limit))
        $backtrace = debug_backtrace();
        trigger_error('Invalid $limit `'.$limit.'` provided to '.__function__.'() in '.
                '`'.$backtrace[0]['file'].'` on line '.$backtrace[0]['line'].'. Expecting an '.
                'integer', E_USER_WARNING);
        return $subject;
    

    // Invalid $limit provided. Throw a warning.
    if($limit<-1)
        $backtrace = debug_backtrace();
        trigger_error('Invalid $limit `'.$limit.'` provided to '.__function__.'() in '.
                '`'.$backtrace[0]['file'].'` on line '.$backtrace[0]['line'].'. Expecting -1 or '.
                'a positive integer', E_USER_WARNING);
        return $subject;
    

    // No replacements necessary. Throw a notice as this was most likely not the intended
    // use. And, if it was (e.g. part of a loop, setting $limit dynamically), it can be
    // worked around by simply checking to see if $limit===0, and if it does, skip the
    // function call (and set $count to 0, if applicable).
    if($limit===0)
        $backtrace = debug_backtrace();
        trigger_error('Invalid $limit `'.$limit.'` provided to '.__function__.'() in '.
                '`'.$backtrace[0]['file'].'` on line '.$backtrace[0]['line'].'. Expecting -1 or '.
                'a positive integer', E_USER_NOTICE);
        return $subject;
    

    // Use str_replace() whenever possible (for performance reasons)
    if($limit===-1)
        return str_replace($search, $replace, $subject, $count);
    

    if(is_array($subject))

        // Loop through $subject values and call this function for each one.
        foreach($subject as $key => $this_subject)

            // Skip values that are arrays (to match str_replace()).
            if(!is_array($this_subject))

                // Call this function again for
                $this_function = __FUNCTION__;
                $subject[$key] = $this_function(
                        $search,
                        $replace,
                        $this_subject,
                        $this_count,
                        $limit
                );

                // Adjust $count
                $count += $this_count;

                // Adjust $limit, if not -1
                if($limit!=-1)
                    $limit -= $this_count;
                

                // Reached $limit, return $subject
                if($limit===0)
                    return $subject;
                

            

        

        return $subject;

     elseif(is_array($search))
        // Only treat $replace as an array if $search is also an array (to match str_replace())

        // Clear keys of $search (to match str_replace()).
        $search = array_values($search);

        // Clear keys of $replace, if applicable (to match str_replace()).
        if(is_array($replace))
            $replace = array_values($replace);
        

        // Loop through $search array.
        foreach($search as $key => $this_search)

            // Don't support multi-dimensional arrays (to match str_replace()).
            $this_search = strval($this_search);

            // If $replace is an array, use the value of $replace[$key] as the replacement. If
            // $replace[$key] doesn't exist, just an empty string (to match str_replace()).
            if(is_array($replace))
                if(array_key_exists($key, $replace))
                    $this_replace = strval($replace[$key]);
                 else 
                    $this_replace = '';
                
             else 
                $this_replace = strval($replace);
            

            // Call this function again for
            $this_function = __FUNCTION__;
            $subject = $this_function(
                    $this_search,
                    $this_replace,
                    $subject,
                    $this_count,
                    $limit
            );

            // Adjust $count
            $count += $this_count;

            // Adjust $limit, if not -1
            if($limit!=-1)
                $limit -= $this_count;
            

            // Reached $limit, return $subject
            if($limit===0)
                return $subject;
            

        

        return $subject;

     else 
        $search = strval($search);
        $replace = strval($replace);

        // Get position of first $search
        $pos = strpos($subject, $search);

        // Return $subject if $search cannot be found
        if($pos===false)
            return $subject;
        

        // Get length of $search, to make proper replacement later on
        $search_len = strlen($search);

        // Loop until $search can no longer be found, or $limit is reached
        for($i=0;(($i<$limit)||($limit===-1));$i++)

            // Replace 
            $subject = substr_replace($subject, $replace, $pos, $search_len);

            // Increase $count
            $count++;

            // Get location of next $search
            $pos = strpos($subject, $search);

            // Break out of loop if $needle
            if($pos===false)
                break;
            

        

        // Return new $subject
        return $subject;

    


【讨论】:

如果你问我有点臃肿。此外,我对这个解决方案最“讨厌”的是错误处理。如果您传递不正确的值,它会破坏脚本。您认为它看起来很专业,但事实并非如此,而不是错误产生通知或警告。更好的是跳过废话,改为返回 false 或 null 并且永远不要在这样的函数中使用回溯。最好的解决方案是程序员可以决定当输出错误/意外时该怎么做。 @Erwinus 这始终使用E_USER_WARNING,这是一个警告不是一个错误。回溯对于找出首先将无效数据传递给函数的代码非常有用(这对于跟踪生产中的错误是绝对必要的)。至于返回 $subject 而不是 false/null 或抛出错误,这只是我用例的个人选择。为了匹配str_replace() 的功能,最好使用可捕获的致命错误(就像str_replace() 在为前两个参数提供闭包时所做的那样)。 啊,没有注意到您正在使用的 E_USER_WARNING,对此感到抱歉。返回主题的问题是你永远看不到函数之外有什么问题。也就是说,如果你做得更聪明(这是可能的),函数的大小可以减半。其次,当 cmets 解释一些复杂的东西时很好,但对于增加值这样的简单事情不是很有用。总的来说,我认为它是不必要的巨大。此外,当您在默认情况下不抑制运行时消息(日志)的服务器上使用此代码时,在生产环境中使用警告可能是安全问题。 @Erwinus 当谈到 cmets 时,我很冗长,因为有些人不懂这种语言,而其他人总是可以删除 cmets。如果您知道为所有边缘情况获得相同最终结果的更好方法,请务必编辑答案。如果您的生产环境不抑制错误消息,那么您将遇到比此功能更大的问题;) TL;DR 这个 sn-p 太臃肿了,我无法想象在正则表达式函数上选择它(我讨厌滚动)。如果你想计算所做的替换,preg_replace() 中有一个参数。此外,preg_replace()/regex 提供了字边界处理(如果需要)——非正则表达式函数无法优雅地提供这些功能。【参考方案13】:

根据我的测试结果,我想投票给 karim79 提供的 regular_express 。 (我现在没有足够的声望来投票!)

zombat 的解决方案使用了太多的函数调用,我什至简化了代码。我正在使用 PHP 5.4 运行这两种解决方案 100,000 次,结果如下:

$str = 'Hello abc, have a nice day abc! abc!';
$pos = strpos($str, 'abc');
$str = substr_replace($str, '123', $pos, 3);

==> 1.85 秒

$str = 'Hello abc, have a nice day abc! abc!';
$str = preg_replace('/abc/', '123', $str, 1);

==> 1.35 秒

如您所见。 preg_replace 的性能并没有很多人想象的那么差。因此,如果您的常规快递不复杂,我建议您使用经典的解决方案。

【讨论】:

您的第一个 sn-p 是不公平的比较,因为它没有使用正确的实现。你没有在$pos 中检查false,所以当大海捞针不存在时,它会损坏输出。 谢谢@mickmackusa,你是对的。但这不是重点。我说简化这段代码只是为了比较实现的效率。 这正是我的观点。您绝不能进行不执行完全相同过程的基准比较。将苹果与半个橙子进行比较是没有用的。完全实施完整的非正则表达式方法将使速度差异更加深刻。 好吧,再次感谢。但我想要的是找到更好的实现方式,而不是产生更深远的影响。【参考方案14】:

为了扩展 zombat 的答案(我相信这是最好的答案),我创建了他的函数的递归版本,它接受 $limit 参数来指定要替换的出现次数。

function str_replace_limit($haystack, $needle, $replace, $limit, $start_pos = 0) 
    if ($limit <= 0) 
        return $haystack;
     else 
        $pos = strpos($haystack,$needle,$start_pos);
        if ($pos !== false) 
            $newstring = substr_replace($haystack, $replace, $pos, strlen($needle));
            return str_replace_limit($newstring, $needle, $replace, $limit-1, $pos+strlen($replace));
         else 
            return $haystack;
        
    

【讨论】:

注意,$start_pos没有质量检查,所以如果超出字符串长度,这个函数会生成:Warning: strpos(): Offset not contained in string...。当$start_pos 超出长度时,此函数无法进行替换。失败证明:3v4l.org/qGuVIR ...您的函数可以结合return $haystack 条件并避免像这样声明一次性变量:3v4l.org/Kdmqp 但是,正如我在本页其他地方的 cmets 中所说,我会而是使用非常干净、直接、非递归的preg_replace() 调用。 是的,这样您就可以添加此行else statment $start_pos &gt; strlen($haystack) ? $start_pos = strlen($haystack) : '';【参考方案15】:

对于一个字符串

$string = 'OOO.OOO.OOO.S';
$search = 'OOO';
$replace = 'B';

//replace ONLY FIRST occurance of "OOO" with "B"
    $string = substr_replace($string,$replace,0,strlen($search));
    //$string => B.OOO.OOO.S

//replace ONLY LAST occurance of "OOOO" with "B"
    $string = substr_replace($string,$replace,strrpos($string,$search),strlen($search)) 
    //$string => OOO.OOO.B.S

    //replace ONLY LAST occurance of "OOOO" with "B"
    $string = strrev(implode(strrev($replace),explode(strrev($search),strrev($string),2)))
    //$string => OOO.OOO.B.S

对于单个字符

$string[strpos($string,$search)] = $replace;


//EXAMPLE

$string = 'O.O.O.O.S';
$search = 'O';
$replace = 'B';

//replace ONLY FIRST occurance of "O" with "B" 
    $string[strpos($string,$search)] = $replace;  
    //$string => B.O.O.O.S

//replace ONLY LAST occurance of "O" with "B" 
    $string[strrpos($string,$search)] = $replace; 
    // $string => B.O.O.B.S

【讨论】:

当搜索字符串不在输入字符串的偏移量 0 处时,第一个 substr_replace() sn-p 失败。失败证明:3v4l.org/oIbRv 当搜索值不存在时,substr_replace() 两种技术都会损坏输入字符串。失败证明:3v4l.org/HmEml(所有rev 调用的最后一项技术非常复杂/难以理解。)【参考方案16】:

补充人们所说的,记住整个字符串是一个数组:

$string = "Lorem ipsum lá lá lá";

$string[0] = "B";

echo $string;

"Borem ipsum lá lá lá"

【讨论】:

除非它包含多字节字符......然后你的技术就会失败。不幸的是,您提供了一个包含á 的示例输入字符串。 Demonstration of failure 您可以使用mb_strlen($subject) != strlen($subject) 验证您的string 是否为多字节字符串 这篇文章并不试图回答所提出的问题。【参考方案17】:

这个函数很大程度上受到@renocor 答案的启发。 它使函数多字节安全。

function str_replace_limit($search, $replace, $string, $limit)

    $i = 0;
    $searchLength = mb_strlen($search);

    while(($pos = mb_strpos($string, $search)) !== false && $i < $limit)
    
        $string = mb_substr_replace($string, $replace, $pos, $searchLength);
        $i += 1;
    

    return $string;


function mb_substr_replace($string, $replacement, $start, $length = null, $encoding = null)

    $string = (array)$string;
    $encoding = is_null($encoding) ? mb_internal_encoding() : $encoding;
    $length = is_null($length) ? mb_strlen($string) - $start : $length;

    $string = array_map(function($str) use ($replacement, $start, $length, $encoding)

        $begin = mb_substr($str, 0, $start, $encoding);
        $end = mb_substr($str, ($start + $length), mb_strlen($str), $encoding);

        return $begin . $replacement . $end;

    , $string);

    return ( count($string) === 1 ) ? $string[0] : $string;

【讨论】:

【参考方案18】:

你可以用这个:

function str_replace_once($str_pattern, $str_replacement, $string) 

        if (strpos($string, $str_pattern) !== false) 
            $occurrence = strpos($string, $str_pattern); 
            return substr_replace($string, $str_replacement, strpos($string, $str_pattern), strlen($str_pattern)); 
         

        return $string; 
     

从 php.net 找到这个例子

用法:

$string = "Thiz iz an examplz";
var_dump(str_replace_once('z','Z', $string)); 

输出:

ThiZ iz an examplz

这可能会稍微降低性能,但最简单的解决方案。

【讨论】:

如果那是输出,那又有什么意义呢?它不应该只用大写“Z”替换第一个小写“z”吗?而不是全部替换?我以为这就是我们在这里谈论的...... 我的错,它只会替换第一次出现。已编辑。 在将近 3 年前,Bas 已经提供了同样的建议(并且没有过度调用strpos())。被否决,因为它不会为页面添加任何新价值。【参考方案19】:

For循环解决方案

<?php
echo replaceFirstMatchedChar("&", "?", "/property/details&id=202&test=123#tab-6");

function replaceFirstMatchedChar($searchChar, $replaceChar, $str)

    for ($i = 0; $i < strlen($str); $i++) 

        if ($str[$i] == $searchChar) 
            $str[$i] = $replaceChar;
            break;
        
    
    return $str;

【讨论】:

【参考方案20】:
$str = "Hello there folks!"
$str_ex = explode("there, $str, 2);   //explodes $string just twice
                                      //outputs: array ("Hello ", " folks")
$str_final = implode("", $str_ex);    // glues above array together
                                      // outputs: str("Hello  folks")

还有一个额外的空间,但没关系,因为在我的情况下它是用于背景脚本的。

【讨论】:

这项技术是由 toouchphp 在 2009 提供的!我投了反对票,因为你的帖子没有给研究人员增加新的价值。在发布答案之前,请确保您的解决方案对页面是唯一的,并为页面增加价值。【参考方案21】:

如果您的字符串不包含任何多字节字符并且您只想替换一个字符,您可以简单地使用strpos

这里是一个处理错误的函数

/**
 * Replace the first occurence of given string
 *
 * @param  string $search  a char to search in `$subject`
 * @param  string $replace a char to replace in `$subject`
 * @param  string $subject
 * @return string
 *
 * @throws InvalidArgumentException if `$search` or `$replace` are invalid or if `$subject` is a multibytes string
 */
function str_replace_first(string $search , string $replace , string $subject) : string 
    // check params
    if(strlen($replace) != 1 || strlen($search) != 1) 
        throw new InvalidArgumentException('$search & $replace must be char');
    elseif(mb_strlen($subject) != strlen($subject))
        throw new InvalidArgumentException('$subject is an multibytes string');
    
    // search 
    $pos = strpos($subject, $search);
    if($pos === false) 
        // not found
        return $subject;
    

    // replace
    $subject[$replace] = $subject;

    return $subject;

【讨论】:

行 $subject[$replace] = $subject; 上的偏移量错误,$replace 是此函数中的字符串【参考方案22】:

这是我创建的一个简单类,用于包装我们稍作修改的 str_replace() 函数。

我们的 php::str_rreplace() 函数还允许您执行反向的、有限的 str_replace(),这在尝试仅替换字符串的最后 X 个实例时非常方便。

这些示例都使用preg_replace()。

<?php
class php 

    /**
    * str_replace() from the end of a string that can also be limited e.g. replace only the last instance of '</div>' with ''
    *
    * @param string   $find
    * @param string   $replace
    * @param string   $subject
    * @param int      $replacement_limit | -1 to replace all references
    *
    * @return string
    */
    public static function str_replace($find, $replace, $subject, $replacement_limit = -1) 
        $find_pattern = str_replace('/', '\/', $find);
        return preg_replace('/' . $find_pattern . '/', $replace, $subject, $replacement_limit);
    

    /**
    * str_replace() from the end of a string that can also be limited e.g. replace only the last instance of '</div>' with ''
    *
    * @param string   $find
    * @param string   $replace
    * @param string   $subject
    * @param int      $replacement_limit | -1 to replace all references
    *
    * @return string
    */
    public static function str_rreplace($find, $replace, $subject, $replacement_limit = -1) 
        return strrev( self::str_replace(strrev($find), strrev($replace), strrev($subject), $replacement_limit) );
    

【讨论】:

您的帖子不会为这个已经饱和的页面增加价值。您的正则表达式解决方案在许多边缘情况下都失败了,因为您使用了不正确的工具来转义针字符串中的字符。失败证明:3v4l.org/dTdYK 来自 2009 的高度赞成和接受的答案已经表明该技术的正确执行。您的第二种方法没有回答所提出的问题,并且已由 oLinkWebDevelopment 提供。

以上是关于使用 str_replace 使其仅作用于第一个匹配项?的主要内容,如果未能解决你的问题,请参考以下文章

过滤现有项目组,使其仅包含符合某些条件的文件

如何处理弹性beantalk部署,使其仅上传更改的文件

从数据库中过滤记录,使其仅显示具有真实值的记录

在 GWT 中,如何调整菜单项的大小以使其仅限于菜单栏

语义 UI 搜索栏 - 您可以截断描述,使其仅显示前几个词,但仍搜索所有词吗?

让useEffect监视条件并使其仅触发一次