【问题标题】:Can I replace groups in Java regex?我可以替换 Java 正则表达式中的组吗?
【发布时间】:2010-11-02 14:02:28
【问题描述】:

我有这段代码,我想知道我是否只能替换 Java 正则表达式中的组(不是所有模式)。 代码:

 //...
 Pattern p = Pattern.compile("(\\d).*(\\d)");
    String input = "6 example input 4";
    Matcher m = p.matcher(input);
    if (m.find()) {

        //Now I want replace group one ( (\\d) ) with number 
       //and group two (too (\\d) ) with 1, but I don't know how.

    }

【问题讨论】:

  • 您能否澄清您的问题,比如给出该输入的预期输出?

标签: java regex replace regex-group


【解决方案1】:

使用$n(其中n 是一个数字)来引用replaceFirst(...) 中捕获的子序列。我假设您想用文字字符串 "number" 替换第一组,用第一组的值替换第二组。

Pattern p = Pattern.compile("(\\d)(.*)(\\d)");
String input = "6 example input 4";
Matcher m = p.matcher(input);
if (m.find()) {
    // replace first number with "number" and second number with the first
    String output = m.replaceFirst("number $3$1");  // number 46
}

为第二组考虑(\D+) 而不是(.*)* 是一个贪婪的匹配器,首先会消耗最后一个数字。当匹配器意识到最终的 (\d) 没有可匹配的内容时,它必须回溯,然后才能匹配到最后一个数字。

【讨论】:

  • 如果你能发布一个示例输出会很好
  • 这适用于第一场比赛,但如果有很多组并且您正在迭代它们一段时间(m.find())
  • 我同意 Hugo,这是实施解决方案的一种糟糕方式......为什么这是公认的答案而不是 acdcjunior 的答案 - 这是完美的解决方案:少量代码,高内聚和低耦合,更少的机会(如果不是没有机会)不需要的副作用... sigh...
  • 此答案当前无效。 m.replaceFirst("number $2$1"); 应该是 m.replaceFirst("number $3$1");
  • 这是回答第一组,而不是 OP 问题“如果我只能替换组”,这与 @acdcjunior 的响应不同
【解决方案2】:

您可以使用Matcher#start(group)Matcher#end(group) 构建通用替换方法:

public static String replaceGroup(String regex, String source, int groupToReplace, String replacement) {
    return replaceGroup(regex, source, groupToReplace, 1, replacement);
}

public static String replaceGroup(String regex, String source, int groupToReplace, int groupOccurrence, String replacement) {
    Matcher m = Pattern.compile(regex).matcher(source);
    for (int i = 0; i < groupOccurrence; i++)
        if (!m.find()) return source; // pattern not met, may also throw an exception here
    return new StringBuilder(source).replace(m.start(groupToReplace), m.end(groupToReplace), replacement).toString();
}

public static void main(String[] args) {
    // replace with "%" what was matched by group 1 
    // input: aaa123ccc
    // output: %123ccc
    System.out.println(replaceGroup("([a-z]+)([0-9]+)([a-z]+)", "aaa123ccc", 1, "%"));

    // replace with "!!!" what was matched the 4th time by the group 2
    // input: a1b2c3d4e5
    // output: a1b2c3d!!!e5
    System.out.println(replaceGroup("([a-z])(\\d)", "a1b2c3d4e5", 2, 4, "!!!"));
}

检查online demo here

【讨论】:

  • 这确实应该是公认的答案,它是最完整和“准备就绪”的解决方案,没有引入与随附代码的耦合级别。尽管我建议更改其中之一的方法名称。乍一看,它看起来像是第一种方法中的递归调用。
  • 错过了编辑机会。收回关于递归调用的部分,没有正确分析代码。重载可以很好地协同工作
  • 这种开箱即用的解决方案仅适用于替换单个出现和一组,并且由于每次替换复制完整字符串对于任何其他目的来说都是非常不理想的。但这是一个很好的起点。可惜Java有很多废话,但缺乏基本的字符串操作工具。
【解决方案3】:

很抱歉打死了,但没有人指出这一点有点奇怪 - “是的,你可以,但这与你在现实生活中使用捕获组的方式相反”。

如果你按原意使用正则表达式,解决方案就这么简单:

"6 example input 4".replaceAll("(?:\\d)(.*)(?:\\d)", "number$11");

或者正如下面的 shmosel 正确指出的那样,

"6 example input 4".replaceAll("\d(.*)\d", "number$11");

...因为在您的正则表达式中根本没有充分的理由对小数进行分组。

您通常不会在要丢弃的字符串部分上使用捕获组,而是在要的字符串部分上使用它们>保持

如果您确实想要替换的组,您可能想要的是模板引擎(例如,moustache、ejs、StringTemplate,...)。


顺便说一句,即使是正则表达式中的非捕获组也只是在正则表达式引擎需要它们识别和跳过可变文本的情况下存在。例如,在

(?:abc)*(capture me)(?:bcd)*

如果您的输入看起来像“abcabccapture mebcdbcd”或“abccapture mebcd”或什至只是“capture me”,则您需要它们。

或者反过来说:如果文本始终相同,而您没有捕捉到它,则根本没有理由使用组。

【讨论】:

  • 不需要非捕获组; \d(.*)\d 就足够了。
  • 我不明白这里的$11。为什么是 11?
  • @Alexis - 这是一个 java 正则表达式怪癖:如果组 11 尚未设置,java 将 $11 解释为 $1 后跟 1。
【解决方案4】:

您可以使用 matcher.start() 和 matcher.end() 方法来获取组位置。所以使用这个位置你可以很容易地替换任何文本。

【讨论】:

    【解决方案5】:

    替换输入中的密码字段:

    {"_csrf":["9d90c85f-ac73-4b15-ad08-ebaa3fa4a005"],"originPassword":["uaas"],"newPassword":["uaas"],"confirmPassword":["uaas"]}
    
    
    
      private static final Pattern PATTERN = Pattern.compile(".*?password.*?\":\\[\"(.*?)\"\\](,\"|}$)", Pattern.CASE_INSENSITIVE);
    
      private static String replacePassword(String input, String replacement) {
        Matcher m = PATTERN.matcher(input);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
          Matcher m2 = PATTERN.matcher(m.group(0));
          if (m2.find()) {
            StringBuilder stringBuilder = new StringBuilder(m2.group(0));
            String result = stringBuilder.replace(m2.start(1), m2.end(1), replacement).toString();
            m.appendReplacement(sb, result);
          }
        }
        m.appendTail(sb);
        return sb.toString();
      }
    
      @Test
      public void test1() {
        String input = "{\"_csrf\":[\"9d90c85f-ac73-4b15-ad08-ebaa3fa4a005\"],\"originPassword\":[\"123\"],\"newPassword\":[\"456\"],\"confirmPassword\":[\"456\"]}";
        String expected = "{\"_csrf\":[\"9d90c85f-ac73-4b15-ad08-ebaa3fa4a005\"],\"originPassword\":[\"**\"],\"newPassword\":[\"**\"],\"confirmPassword\":[\"**\"]}";
        Assert.assertEquals(expected, replacePassword(input, "**"));
      }
    

    【讨论】:

      【解决方案6】:

      这是一个不同的解决方案,它还允许在多个匹配项中替换单个组。 它使用堆栈来反转执行顺序,因此可以安全地执行字符串操作。

      private static void demo () {
      
          final String sourceString = "hello world!";
      
          final String regex = "(hello) (world)(!)";
          final Pattern pattern = Pattern.compile(regex);
      
          String result = replaceTextOfMatchGroup(sourceString, pattern, 2, world -> world.toUpperCase());
          System.out.println(result);  // output: hello WORLD!
      }
      
      public static String replaceTextOfMatchGroup(String sourceString, Pattern pattern, int groupToReplace, Function<String,String> replaceStrategy) {
          Stack<Integer> startPositions = new Stack<>();
          Stack<Integer> endPositions = new Stack<>();
          Matcher matcher = pattern.matcher(sourceString);
      
          while (matcher.find()) {
              startPositions.push(matcher.start(groupToReplace));
              endPositions.push(matcher.end(groupToReplace));
          }
          StringBuilder sb = new StringBuilder(sourceString);
          while (! startPositions.isEmpty()) {
              int start = startPositions.pop();
              int end = endPositions.pop();
              if (start >= 0 && end >= 0) {
                  sb.replace(start, end, replaceStrategy.apply(sourceString.substring(start, end)));
              }
          }
          return sb.toString();       
      }
      

      【讨论】:

        猜你喜欢
        • 2017-02-05
        • 2016-11-12
        • 2010-12-24
        • 2014-09-09
        • 1970-01-01
        • 1970-01-01
        • 2021-02-12
        • 2012-03-20
        • 1970-01-01
        相关资源
        最近更新 更多