“*”在正则表达式中的特殊作用

Peculiar working of '*' in regex expression

在编写用于将所有连续“1”和单个“1”替换为 's' 的正则表达式模式时。我发现这很令人困惑,使用“+”(用于匹配 1 个或多个)给出了预期的结果,但是“*”给出了奇怪的结果

>>> l='100'
>>> import re
>>> j=re.compile(r'(1)*')    
>>> m=j.sub('*',l)
>>> m
'*0*0*'

虽然使用“+”给出了预期的结果。

>>> l='100'
>>> j=re.compile(r'1+')
>>> m=j.sub('*',l)
>>> m
'*00'

正则表达式中的“*”是如何给出这个的,而它的行为是匹配 0 个或多个。

(1)* 表示 "match 0 or more 1's"。因此,对于 100,它匹配 1、0 和 0 之间的空字符串以及最后一个 0 之后的空字符串。然后用“*”替换空字符串。 1+要求至少有一个1匹配,所以不会匹配字符之间的边界

对于那些好奇的读者,是的,python 输出是 *0*0* 而不是 **0*0*。这是一个可以玩的测试 python script。 (Regex101 对此有错误的输出,因为它没有使用实际的 python 正则表达式引擎。在线正则表达式测试人员通常会使用 PCRE(在 PHP 和 Apache HTTP 服务器中提供),并伪造目标正则表达式引擎。始终在实时代码中测试您的正则表达式!)

在这里你可以看到 JavaScript 输出将是 **0*0* (它将匹配 1 和 0 之间的空字符串作为新匹配)这是为什么 'regex flavor' 很重要。不同的引擎使用略有不同的规则。 (在这种情况下,如果新匹配从 0 或字符边界开始)

console.log("100".replace(/(1)*/g, '*'))

谨防无法匹配的模式。这没有明确定义,因此行为因引擎而异。例如,您在 Perl 中得到不同的结果。

$ perl -e'CORE::say "100" =~ s/1*/\*/rg'
**0*0*
  1. 在位置0,它匹配1个字符。
  2. 在位置1,它匹配0个字符。
  3. [强制前进避免无限循环]
  4. 在位置2,它匹配0个字符。
  5. [强制前进避免无限循环]
  6. 位置3,匹配0个字符。
  7. [强制前进避免无限循环]
  8. [匹配失败:超出字符串结尾]
regex = r"1*"
p = re.compile(regex)
test_str = "100"
for m in p.finditer(test_str):
    print(m.start(), m.group())

输出 4 个匹配项(这就是 regex101 显示 4 个匹配项的原因):

0 1
1 
2 
3 

虽然 re.sub() 替换了 3 个位置,这是 re.sub() 在零长度匹配 (Python doc) 后前进的原因:

sub(pattern, repl, string, count=0, flags=0)

Return the string obtained by replacing the leftmost non-overlapping occurrences of pattern in string by the replacement repl.

...

Empty matches for the pattern are replaced only when not adjacent to a previous match, so sub('x*', '-', 'abc') returns '-a-b-c-'.

非重叠事件是什么意思? It means when:

the first match ended at the start of the string, where the first match attempt began. The regex engine needs a way to avoid getting stuck in an infinite loop that forever finds the same zero-length match at the start of the string.

The simplest solution, which is used by most regex engines, is to start the next match attempt one character after the end of the previous match, if the previous match was zero-length.

在这种情况下,第二次匹配尝试从字符串中 1 和 0 之间的位置开始,因此存在差异。