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

时间:2011-10-23 15:45:49

标签: regex perl lookahead lookbehind regex-lookarounds

我在使用正则表达式中的嵌套'+'/' - 'lookahead / lookbehind时遇到问题。

假设我要更改'*'字符串中的'%',让我们说'\'转义下一个字符。 (将正则表达式转换为sql,如命令^^)。

所以字符串

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

我试过了:

(?<!\\)(?=\\\\)*\*      but this doesn't work
(?<!\\)((?=\\\\)*\*)    ...
(?<!\\(?=\\\\)*)\*      ...
(?=(?<!\\)(?=\\\\)*)\*  ...

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

(?<!\\(?=\\\\)*)\*(?=(?<!\\)(?=\\\\)*)\*之间有什么区别,或者这些具有这种视觉构造的正则表达式之间的区别是否真的错了?

5 个答案:

答案 0 :(得分:11)

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

(?<=(?<!\\)(?:\\\\)*)\*        # this is explained in Tim Pietzcker' answer

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

(?=(?<!\\)(?:\\\\)*\*)(\\*)\*  # also look at ridgerunner's improved version

将其替换为第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

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

答案 1 :(得分:9)

好吧,既然蒂姆决定不用我建议的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);

附录:非外观JavaScript解决方案

上述解决方案确实需要lookbehind,因此它无法在JavaScript中运行。以下JavaScript解决方案使用lookbehind:

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

此解决方案将反斜杠 - 任何的每个实例替换为自身,并将*星号的每个实例替换为%百分号。

编辑2011-10-24:修正了Javascript版本,以正确处理以下案例:**text**。 (感谢Alan Moore指出以前版本中的错误。)

答案 2 :(得分:5)

其他人已经展示了如何通过后视来完成这项任务,但我想提出一个不使用外观的案例。考虑这个解决方案( demo here ):

s/\G([^*\\]*(?:\\.[^*\\]*)*)\*/$1%/g;

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

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

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

答案 3 :(得分:3)

所以你基本上想要匹配*只有它前面有偶数个反斜杠(换句话说,如果它没有被转义)?那么你根本不需要先行,因为你只是回头看,不是吗?

搜索

(?<=(?<!\\)(?:\\\\)*)\*

并替换为%

<强>解释

(?<=       # Assert that it's possible to match before the current position...
 (?<!\\)   # (unless there are more backslashes before that)
 (?:\\\\)* # an even number of backslashes
)          # End of lookbehind
\*         # Then match an asterisk

答案 4 :(得分:0)

在正则表达式中检测逃逸反斜杠的问题让我着迷了一段时间,直到最近我才意识到我完全过度复杂了。有几件事情让它变得更简单,据我所知,这里没有人注意到它们:

  • 反斜杠会转义后面的任何字符,而不仅仅是其他反斜杠。所以(\\.)*会吃掉整个转义字符串,无论它们是否反斜杠。您不必担心偶数或奇数斜线;只需在链的开头或结尾检查一个单独的\ ridgerunner的 JavaScript解决方案确实利用了这一点)。

  • Lookarounds并不是确保从链中第一个反斜杠开始的唯一方法。您可以只查找非反斜杠字符(或字符串的开头)。

结果是一个简短的,简单的模式,不需要外观或回调,它比我到目前为止看到的任何其他东西都短。

/(?!<\\)(\\.)*\*/g

替换字符串:

"$1%"

This works in .NET,允许使用lookbehinds,它应该在Perl中适用于你。它可以在JavaScript中完成,但没有lookbehinds或\G锚点,我无法在单行中看到这样做的方法。 Ridgerunner的回调应该有效,循环也是如此:

var regx = /(^|[^\\])(\\.)*\*/g;
while (input.match(regx)) {
    input = input.replace(regx, '$1$2%');
}

我从其他正则表达式问题中识别出很多名字,我知道你们中有些人比我聪明。如果我犯了错误,请说出来。