【问题标题】:Nested regex lookahead and lookbehind嵌套的正则表达式前瞻和后视
【发布时间】:2011-10-23 15:45:49
【问题描述】:

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

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

所以字符串

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

我试过了:

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

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

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

【问题讨论】:

  • 您使用什么语言?你真的希望\*test\* 保持不变而不是变成*test*

标签: regex perl lookahead lookbehind regex-lookarounds


【解决方案1】:

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

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

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

【讨论】:

  • 关于无限长度后视的好点(也是@ridgerunner)。不是每个人都在使用 .NET 或 JGSoft 正则表达式引擎。
【解决方案2】:

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

替换:((?&lt;!\\)(?:\\\\)*)\*$1%

这里是注释PHP sn-pt的形式:

// 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 : '%';
    });

此解决方案将 backslash-anything 的每个实例替换为自身,并将 * 星号的每个实例替换为 % 百分号。

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

【讨论】:

  • +1 用于简化 @Tim 的正则表达式,但您的 JavaScript 安全版本在 **test** 上失败。 :-/ 我认为这在单个 JS replace 操作中是不可行的。
  • @Alan Moore - 非常正确。谢谢敏锐的眼光!但是,这可以通过一个使用回调函数的replace() 来完成。查看最新版本。
【解决方案3】:

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

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

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

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

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

【讨论】:

    【解决方案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
    

    【讨论】:

    • 关闭,但是(如您所知),很少有正则表达式引擎支持可变长度的后视。将lookbehind更改为捕获组$1并将替换字符串更改为:$1%,然后它应该适用于大多数(但仍然不是js)。
    • 嗯,没错。让我们希望他使用.NET,然后:)
    • 现在,由于 bliof 已经指定他正在使用 Perl,我通常会撤回我的答案,因为由于上述限制,它在 Perl 中不起作用。但由于其他答案都引用了这个,我将把它留在这里。
    【解决方案5】:

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

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

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

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

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

    以及替换字符串:

    "$1%"
    

    This works in .NET,它允许后视,它应该在 Perl 中为您工作。可以在 JavaScript 中执行此操作,但如果没有后视或 \G 锚点,我看不到在单行中执行此操作的方法。 Ridgerunner 的回调应该可以工作,循环也可以:

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

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

    【讨论】:

    • 不幸的是,这个正则表达式有问题。当您尝试匹配*\\* 之类的内容时,它将失败(您将第一次以^\* 开头,但第二次将转到[^\\],并且\* 将没有任何匹配项)
    • @bliof - 你是对的。而且我想不出任何方法可以在没有回调或循环的情况下在 JS 风格的正则表达式中解决这个问题。在其他变体中,除了替换*(例如计算转义的反斜杠或其他东西)之外,您正在做的事情仍然有效。我不认为单行可以在 JavaScript 中做到这一点,但我会用一些可以编辑的东西。感谢您纠正我,我有一种强烈的感觉,这太好了,令人难以置信。
    猜你喜欢
    • 2015-09-13
    • 1970-01-01
    • 2013-12-24
    • 1970-01-01
    • 1970-01-01
    • 2021-10-11
    • 1970-01-01
    • 2014-07-05
    • 1970-01-01
    相关资源
    最近更新 更多