替换文本末尾的额外字符

时间:2013-03-29 15:42:43

标签: java php regex pcre

在PHP和Java中,我将/^[^\pL]*|[^\pL]*$/应用于-A-,我得到*A**。我应用了对称模式并获得了不对称结果!为什么?我想知道为什么它的输出不是*A*

模式说除了字符串末尾的字母之外的所有东西都应该被*替换,它也是贪婪的,应该将所有非字母的东西一起替换。

Alos在RegexBuddy中的注释我得到了*A*这就是我的期望。

更新:我简化了问题以集中我的主要关注点。

4 个答案:

答案 0 :(得分:27)

#^[^\pL]+|[^\pL]+$#u

*替换为+。将*$结合使用并不像预期的那样有效。在正则表达式引擎如何工作的奇怪结果中,X*$将为X*找到两个匹配项。使用+修复了它。

解释

[^\pL]*$

让我们看一下正则表达式的这一部分,这部分没有按预期工作。为什么它会在一些字符串的末尾加上两个*

  1. 在替换第一组破折号后,请考虑第三个示例字符串---A---

    *A---$
    
  2. 正则表达式引擎在此处找到正则表达式的匹配项:

    *A---$
      ^
    
  3. 并用星号替换"---"

    *A*$
      ^
    
  4. 然后将其内部光标移动到替换字符串的右侧。

    *A*$
       ^
    
  5. 从此光标位置开始,查找另一场比赛。它找到了一个!它找到"" - 空字符串! ""由0或更多非字母([^\pL]*)组成,并且它锚定在字符串的末尾($),因此它是有效匹配。当然,它找到了空字符串,但这是允许的。

    这是出乎意料的,因为它再次与$锚点匹配。这不是错的吗?它应该不再匹配$,是吗?嗯,实际上,它应该,而且确实如此。它可以再次匹配$,因为$不是输入字符串中的实际字符 - 它是零宽度断言。它不会被第一次更换“用完”。允许$匹配两次。

  6. 因此,它用星号“替换”空字符串""。这就是你最终得到两个星号的原因。

    *A**$
       ^
    
  7. 如果正则表达式引擎返回到第4步,它会找到另一个空字符串并添加另一个星号。从概念上讲,那里有无数个空字符串。为避免这种情况,引擎不允许下一个匹配从与前一个匹配的位置开始。此规则使其无法进入无限循环。

答案 1 :(得分:7)

正确的正则表达式是这样的:

$arr = preg_replace('#^[^\pL]+|[^\pL]+$#','*', 
           array('A','-A-','---A---','-+*A*+-','------------A------------'));

注意+而不是*。这将给出输出:

Array
(
    [0] => A
    [1] => *A*
    [2] => *A*
    [3] => *A*
    [4] => *A*
)

PS:请注意,由于A之前和之后没有非alpha 字符,第一个元素将保持不变。

答案 2 :(得分:2)

运行一次:
在代码之后和代码正文中给出了解释 - 作为注释。

<?php
class String
{
    private $str;
    public function __construct($str)
    {
        $this->str=$str;
    }
    public function replace($regex,$replacement)
    {
        return preg_replace($regex,$replacement,$this->str);
    }
}

function String($str)
{
    return new String($str);
}

echo String('A')->replace('/^[^\pL]*|[^\pL]*$/','*').'<br />';//Outputs *A*
 //Why does this output *A* and not A?
 //Because it successfully matches an empty string
 //The easiest way to test for the presence of an empty string is like so:
echo String('A')->replace('//','*').'<br />';//Outputs *A*
 //The engine begins by placing its internal pointer before the string like so:
 // A
 //^
 //It then tests the regular expression for the empty string ""
 //Most regular expressions will fail this test. But in our case matches it successfully.
 //Since we are preforming a search and replace the "" will get replaced by a "*" character
 //Then the internal pointer advances to the next character after its successful match
 // A
 // ^
 //It tests our regular expression for the A character and it fails.
 //Since we are performing a search and replace the searched "A" portion remains unchanged as "A"
 //The internal pointer advances to the next character
 // A
 //  ^
 //It tests our regular expression for the empty string ""
 //Again, most regular expressions will fail this test. But since ours successfully matched it,
 //The "" portion will get replaced by "*"
 //The engine then returns our output:
 //*A*
echo '<hr />';
 //If we wanted to replace the A character too, we'd do this:
echo String('A')->replace('/|A/','*').'<br />';//Outputs ***
 //Or we could do:
echo String('A')->replace('/.*?/','*').'<br />';//Outputs ***
 //Thus we see for a 1 character string the engine will test for the empty spaces "" before and after the character as well
 //For a 19 character string it tests for all the gaps between each character like so:
echo String('19 character string')->replace('//','*').'<br />';//Outputs *1*9* *c*h*a*r*a*c*t*e*r* *s*t*r*i*n*g*
 //For an empty string it would match once successfully like so:
echo String('')->replace('//','*').'<br />';//Outputs *

echo String('A')->replace('/^[^\pL]*|[^\pL]*$/','*');//Outputs *A*

为什么以上输出*A*而非A
因为此正则表达式成功匹配空字符串"" 使用空的正则表达式观察到相同的行为,如下所示:
echo String('A')->replace('//','*');//Outputs *A*

我现在解释 为什么正则表达式引擎实现产生这些奇怪的结果。之后你会明白,它们根本不是那么奇怪,但实际上是正确的行为。

引擎首先将内部指针放在字符串之前,如下所示:

  A
_ _ _
^

由于指针指向空字符串"",因此它会根据我们的正则表达式对其进行测试 大多数正则表达式将无法通过此测试,因为满足正则表达式所需的最小字符数通常是一个或多个。但在我们的情况下,匹配成功,因为0个字符与我们的正则表达式有效匹配 由于我们正在执行搜索和替换,因此""将替换为"*"字符。
然后内部指针在成功匹配后前进到下一个字符

  A
_ _ _
  ^

它测试"A"字符的正则表达式,失败 由于我们正在执行搜索和替换,因此搜索到的"A"部分保持不变为"A"
内部指针前进到下一个字符

  A
_ _ _
    ^

它测试我们的正则表达式为空字符串""
同样,大多数正则表达式都将无法通过此测试 但由于我们的正则表达式成功与之匹配,""部分将被"*"替换为 引擎然后完成循环遍历字符串"A"返回我们的输出:"*A*"

如果我们也想替换A字符,我们就这样做:
echo String('A')->replace('/|A/','*');//Outputs ***

或者我们可以这样做:
echo String('A')->replace('/.*?/','*').'<br />';//Outputs ***

因此,我们看到一个1字符的字符串引擎将在字符前后测试""

对于19个字符的字符串,它测试每个字符之间的所有间隙,如下所示:
echo String('19 character string')->replace('//','*');
//Outputs *1*9* *c*h*a*r*a*c*t*e*r* *s*t*r*i*n*g*

对于一个空字符串,它将成功匹配一次,如下所示:
echo String('')->replace('//','*');//Outputs *

我的解释得出结论。要修复正则表达式,请按照之前的建议操作并使用:
/^[^\pL]+|[^\pL]+$/
这将使满足正则表达式所需的最小字符数为1,从而解决不需要的行为。

作为最后一句话,如果有人想知道\pL在正则表达式中做了什么,它基本上意味着:匹配任何类似字母的字符(而不是数字或符号)。这里解释如下: http://www.php.net/manual/en/regexp.reference.unicode.php

答案 3 :(得分:1)

/^[^\pL]*|[^\pL]*$/  
['A','-A-','---A---','-+*A*+-','------------A------------']

也许我误解了问题或正则表达式,但看起来它与两个选项中的一个匹配

选项1:它匹配/^换行符或字符串的开头。然后它匹配一个不是零或更多次字母的字符

理论上-A=A-=-+_+_==-=~````~!@#$A,甚至=-+_+_==-=~~!@#都符合此要求。

选项2:它匹配的字母不是零或更多次,然后匹配字符串或行的结尾