9

我在正则表达式中的嵌套“+”/“-”前瞻/后视时遇到问题。

假设我想更改'*'字符串中的'%'并且假设'\'转义下一个字符。(将正则表达式转换为 sql 之类的命令 ^^)。

所以字符串

  • '*test*'应改为'%test%',
  • '\\*test\\*'-> '\\%test\\%',但是
  • '\*test\*'并且'\\\*test\\\*'应该保持不变。

我试过:

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

与上面给出的示例中的“*”匹配的正确正则表达式是什么?

有什么区别(?<!\\(?=\\\\)*)\*(?=(?<!\\)(?=\\\\)*)\*或者如果这些本质上是错误的,那么具有这种视觉结构的正则表达式之间的区别是什么?

4

5 回答 5

11

要查找未转义字符,您将查找前面有偶数(或零)个转义字符的字符。这是相对直截了当的。

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

不幸的是,许多正则表达式引擎不支持可变长度的look-behind,所以我们必须用look-ahead代替:

(?=(?<!\\)(?:\\\\)*\*)(\\*)\*  # 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

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

于 2011-10-23T16:16:09.580 回答
9

好的,因为 Tim 决定不使用我建议的 mods 更新他的正则表达式(并且 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 指出之前版本中的错误。)

于 2011-10-23T16:46:41.253 回答
5

其他人已经展示了如何通过lookbehind 来完成此操作,但我想说明根本不使用lookarounds 的情况。考虑这个解决方案(这里是演示):

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

大部分的正则表达式 ,[^*\\]*(?:\\.[^*\\]*)*是 Friedl 的“展开循环”习语的一个例子。它使用尽可能多的单个字符,而不是星号或反斜杠,或由反斜杠后跟任何内容的字符对。这允许它避免使用未转义的星号,无论它们前面有多少转义的反斜杠(或其他字符)。

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

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

于 2011-10-23T23:39:10.850 回答
4

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

搜索

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

并替换为%.

解释:

(?<=       # 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
于 2011-10-23T16:01:27.583 回答
0

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

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

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

结果是一个简短的、简单的模式,不需要环视或回调,而且它比我目前看到的任何其他模式都要短。

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

和替换字符串:

"$1%"

这适用于 .NET,它允许后视,它应该适用于您在 Perl 中。可以在 JavaScript 中做到这一点,但如果没有后视或\G锚点,我看不到在单行中做到这一点的方法。Ridgerunner 的回调应该可以工作,循环也可以:

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

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

于 2012-10-16T20:38:43.317 回答