【问题标题】:Java replaceAll regex errorJava replaceAll 正则表达式错误
【发布时间】:2015-05-06 12:13:24
【问题描述】:

我想将所有“*”转换为“.*”,除了“\*”

String regex01 = "\\*toto".replaceAll("[^\\\\]\\*", ".*");
assertTrue("*toto".matches(regex01));// True

String regex02 = "toto*".replaceAll("[^\\\\]\\*", ".*");
assertTrue("tototo".matches(regex02));// True

String regex03 = "*toto".replaceAll("[^\\\\]\\*", ".*");
assertTrue("tototo".matches(regex03));// Error

如果“*”是第一个字符,则会发生错误: java.util.regex.PatternSyntaxException: 在索引 0 附近悬挂元字符“*”

什么是正确的正则表达式?

【问题讨论】:

    标签: java regex replaceall


    【解决方案1】:

    这是目前唯一能够连续处理多个转义\的解决方案:

    String regex = input.replaceAll("\\G((?:[^\\\\*]|\\\\[\\\\*])*)[*]", "$1.*");
    

    工作原理

    让我们打印字符串regex 来看看正则表达式引擎正在解析的实际字符串:

    \G((?:[^\\*]|\\[\\*])*)[*]
    

    ((?:[^\\*]|\\[\\*])*) 匹配不是\* 或转义序列\\\* 的字符序列。我们匹配所有我们不想碰的字符,然后把它放在一个捕获组中,这样我们就可以把它放回去了。

    [*] 所述,上述序列后跟一个未转义的星号。

    为了确保当正则表达式无法匹配未转义的* 时我们不会“跳转”,\G 用于确保下一个匹配只能从字符串的开头开始,或从最后一场比赛结束的地方。

    为什么要这么长的解决方案? 有必要,因为 look-behind 构造检查* 之前的连续\ 的数量是否为奇数甚至不受 Java 正则表达式的官方支持。因此,我们需要从左到右使用字符串,同时考虑转义序列,直到遇到未转义的*,并将其替换为.*

    测试程序

    String inputs[] = {
        "toto*",
        "\\*toto",
        "\\\\*toto",
        "*toto",
        "\\\\\\\\*toto",
        "\\\\*\\\\\\*\\*\\\\\\\\*"};
    
    for (String input: inputs) {
        String regex = input.replaceAll("\\G((?:[^\\\\*]|\\\\[\\\\*])*)[*]", "$1.*");
        System.out.println(input);
        System.out.println(Pattern.compile(regex));
        System.out.println();
    }
    

    样本输出

    toto*
    toto.*
    
    \*toto
    \*toto
    
    \\*toto
    \\.*toto
    
    *toto
    .*toto
    
    \\\\*toto
    \\\\.*toto
    
    \\*\\\*\*\\\\*
    \\.*\\\*\*\\\\.*
    

    【讨论】:

      【解决方案2】:

      你需要在这里使用否定的lookbehind:

      String regex01 = input.replaceFirst("(?<!\\\\)\\*", ".*");
      

      (?&lt;!\\\\) 是一个否定的lookbehind,表示如果* 前面没有反斜杠,则匹配。

      例子:

      regex01 = "\\*toto".replaceAll("(?<!\\\\)\\*", ".*");
      //=> \*toto
      
      regex01 = "*toto".replaceAll("(?<!\\\\)\\*", ".*");
      //=> .*toto
      

      【讨论】:

      • @gregouille 是否有可能在转义\ 后放置*?像"\\\\*" 这样的东西?如果是,预期结果是什么?
      • 是的,这是可能的,但这是我问题的第二部分,谢谢
      • @gregouille:Pshemo 提出的案例在我的回答中得到了处理。
      • @gregouille:这纯粹是基于这里的 cmets,因为它不是您的问题的一部分。如果有可能也使用"\\\\* 转义反斜杠,就像 Pshemo 提到的那样,那么您可以使用input.replaceAll("(?&lt;!(?&lt;!\\\\)\\\\)\\*", ".*");
      • @anubhava:这是一个肮脏的解决方案,它假设输入中可以有最大数量的\。每当有转义语法时,像这样进行“窥视孔”后视充其量只是一个不完整的解决方案。
      【解决方案3】:

      您必须满足正则表达式中以* 开头的字符串的情况:

      (^|[^\\\\])\\*
      

      单个插入符号表示“字符串的开头”(“开始锚点”)。

      编辑

      除了上面的更正之外,replaceAll 调用中的替换字符串必须是 $1.* 而不是 .*,以免丢失未转义的 * 之前的匹配字符。

      【讨论】:

      • 这实际上将在此过程中将"a*toto" 替换为".*toto" 吃掉* 之前的字符。
      • @anubhava 谢谢,我错过了。答案已补充。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-05-08
      • 1970-01-01
      • 2019-01-05
      • 1970-01-01
      • 1970-01-01
      • 2020-03-12
      • 1970-01-01
      相关资源
      最近更新 更多