PHP如何仅将字符串末尾连续问号/感叹号替换为单个对应符号
解决方案
你原有代码的问题是正则没有锚定字符串末尾,导致全局替换所有位置的连续标点。以下两种方案都可以满足需求:
方案1:正则实现(代码最简洁)
核心逻辑是通过末尾锚定符$,仅匹配字符串最结尾的连续问号/感叹号,通过捕获组保留单个对应符号,完全不会改动句中内容。
<?php /** * 仅处理句尾连续问号/感叹号为单个 * @param string $text 待处理文本 * @return string 处理后文本 */ function processEndPunctuation(string $text): string { // 正则解释: // ([?!]) 捕获第一个问号或感叹号 // \1+ 匹配1个及以上和捕获内容完全相同的字符(即连续同标点) // $ 锚定字符串末尾,仅匹配结尾处的符合规则的内容 // u 修饰符兼容UTF-8编码 return preg_replace('/([?!])\1+$/u', '$1', $text); } // 测试用例验证 $testCases = [ "What went wrong?????????", "Oh my goodness!!!", "I just!!! can!!! not!!! believe!!! it!!!", "Oh my goodness!", "I just cannot believe it.", "hgj dfsg jsdgfjg??? jgsdjfgj ??? jkjsdfgjfk ??" ]; foreach ($testCases as $case) { echo processEndPunctuation($case) . PHP_EOL; } ?>
测试输出结果完全符合需求:
What went wrong?Oh my goodness!I just!!! can!!! not!!! believe!!! it!Oh my goodness!I just cannot believe it.hgj dfsg jsdgfjg??? jgsdjfgj ??? jkjsdfgjfk ?
方案2:非正则遍历实现
如果不想用正则,可以从字符串尾部往前遍历判断处理,逻辑更直观:
<?php function processEndPunctuationWithoutRegex(string $text): string { $len = mb_strlen($text); // 长度小于2不可能有连续结尾标点,直接返回 if ($len < 2) { return $text; } // 取最后一个字符 $lastChar = mb_substr($text, -1); // 最后一个字符不是目标标点,直接返回 if ($lastChar !== '?' && $lastChar !== '!') { return $text; } // 从倒数第二位往前找连续相同标点的截止位置 $cutPos = $len - 1; for ($i = $len - 2; $i >= 0; $i--) { $currentChar = mb_substr($text, $i, 1); if ($currentChar === $lastChar) { $cutPos = $i; } else { break; } } // 截止位置在倒数第二位及以后,说明结尾有连续标点,截断后补单个目标标点 if ($cutPos < $len - 1) { return mb_substr($text, 0, $cutPos) . $lastChar; } // 结尾只有单个标点,直接返回原内容 return $text; } ?>
该方案输出结果和正则方案完全一致,兼容多字节编码场景。
注意:如果业务场景需要先忽略句尾的空白字符(比如结尾有空格、换行再跟标点),可以在处理前先对文本执行
rtrim($text, " \t\n\r\0\x0B")去掉尾部空白再走上述逻辑即可。
内容的提问来源于stack exchange,提问作者dinesh
相关产品推荐
相关产品推荐

