嵌套的正则表达式前瞻和后瞻

bli*_*iof 9 regex perl lookahead lookbehind regex-lookarounds

我在正则表达式中嵌套的'+'/' - 'lookahead/lookbehind有问题.

假设我想改变'*'一个字符串,'%'让我们说它'\'逃脱了下一个字符.(将正则表达式转换为sql,如命令^^).

所以字符串

  • '*test*'应改为'%test%',
  • '\\*test\\*'- > '\\%test\\%',但是
  • '\*test\*''\\\*test\\\*'应保持不变.

我试过了:

(?<!\\)(?=\\\\)*\*      but this doesn't work
(?<!\\)((?=\\\\)*\*)    ...
(?<!\\(?=\\\\)*)\*      ...
(?=(?<!\\)(?=\\\\)*)\*  ...
Run Code Online (Sandbox Code Playgroud)

在上面给出的例子中,正确的正则表达式与'*'相匹配是什么?

是什么区别(?<!\\(?=\\\\)*)\*(?=(?<!\\)(?=\\\\)*)\*,或者如果这些人基本上是错误的有这样的可视化构造正则表达式的区别?

Tom*_*lak 11

要查找未转义的字符,您需要查找一个前面有偶数个(或零)转义字符的字符.这是相对简单的.

(?<=(?<!\\)(?:\\\\)*)\*        # this is explained in Tim Pietzcker' answer
Run Code Online (Sandbox Code Playgroud)

不幸的是,许多正则表达式引擎不支持可变长度的后视,所以我们必须替换前瞻:

(?=(?<!\\)(?:\\\\)*\*)(\\*)\*  # also look at ridgerunner's improved version
Run Code Online (Sandbox Code Playgroud)

将其替换为组1的内容和%符号.

说明

(?=           # start look-ahead
  (?<!\\)     #   a position not preceded by a backslash (via look-behind)
  (?:\\\\)*   #   an even number of backslashes (don't capture them)
  \*          #   a star
)             # end look-ahead. If found,
(             # start group 1
  \\*         #   match any number of backslashes in front of the star
)             # end group 1
\*            # match the star itself
Run Code Online (Sandbox Code Playgroud)

前瞻确保只考虑偶数个反斜杠.无论如何,没有办法将它们匹配到一个组中,因为前瞻不会提升字符串中的位置.


rid*_*ner 9

好吧,因为Tim决定不用我建议的mod更新他的正则表达式(并且Tomalak的答案不是简化的),这是我推荐的解决方案:

替换:((?<!\\)(?:\\\\)*)\*$1%

这是一个注释PHP片段的形式:

// Replace all non-escaped asterisks with "%".
$re = '%             # Match non-escaped asterisks.
    (                # $1: Any/all preceding escaped backslashes.
      (?<!\\\\)      # At a position not preceded by a backslash,
      (?:\\\\\\\\)*  # Match zero or more escaped backslashes.
    )                # End $1: Any preceding escaped backslashes.
    \*               # Unescaped literal asterisk.
    %x';
$text = preg_replace($re, '$1%', $text);
Run Code Online (Sandbox Code Playgroud)

附录:非外观JavaScript解决方案

上面的解决方案确实需要lookbehind,因此它不适用于JavaScript.下面的JavaScript解决方案并没有使用回顾后:

text = text.replace(/(\\[\S\s])|\*/g,
    function(m0, m1) {
        return m1 ? m1 : '%';
    });
Run Code Online (Sandbox Code Playgroud)

此解决方案将每个反斜杠实例替换为自身,并且每个*星号实例都带有%百分号.

编辑2011-10-24:固定的JavaScript版本,以正确处理案件,如:**text**.(感谢Alan Moore指出以前版本中的错误.)


Ala*_*ore 5

其他人已经展示了如何通过外观来完成这项工作,但我想提出一个不使用外观的案例.考虑这个解决方案(这里演示):

s/\G([^*\\]*(?:\\.[^*\\]*)*)\*/$1%/g;
Run Code Online (Sandbox Code Playgroud)

正则表达式的大部分[^*\\]*(?:\\.[^*\\]*)*是Friedl的"展开循环"习惯的一个例子.除了星号或反斜杠之外,它会消耗尽可能多的单个字符,或者由反斜杠后跟任何东西组成的字符对.这使得它可以避免消耗未转义的星号,无论它们前面有多少转义的反斜杠(或其他字符).

\G锚每场比赛到以前的比赛结束,或者输入的开始.如果这是第一次尝试匹配的位置.这可以防止正则表达式引擎简单地跳过转义的反斜杠并匹配未转义的星号.因此,/g受控匹配的每次迭代都会消耗到下一个未转义星号的所有内容,捕获除#1组中的星号之外的所有星号.然后重新插入,并*替换为%.

我认为这至少与外观方法一样可读,并且更容易理解.它确实需要支持\G,所以它不能在JavaScript或Python中工作,但它在Perl中运行得很好.