我可以替换Java正则表达式中的组吗?


95

我有这段代码,我想知道是否可以替换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.

    }

6
您能否澄清您的问题,例如给出该输入的预期输出?
Michael Myers

Answers:


125

使用$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)匹配项时,匹配器将不得不回溯,然后才可以匹配最终的数字。


7
如果您能发布示例输出
那就太好了

6
这在第一场比赛中有效,但是如果有很多组并且您要用一会儿遍历它们(m.find())
雨果·萨拉戈萨,

1
我同意雨果(Hugo)的观点,这是实现解决方案的一种糟糕方法...为什么在地球上,这是公认的答案,而不是令人满意的答案-这是完美的解决方案:代码量少,内聚力强,耦合度低,机会少(如果不是没有机会的话)有害的副作用…… 感叹 ……
FireLight

该答案目前无效。本m.replaceFirst("number $2$1");应该是m.replaceFirst("number $3$1");
丹尼尔Eisenreich

52

您可以使用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, "!!!"));
}

在此处查看在线演示


1
这确实应该是公认的答案,它是最完整且“准备就绪”的解决方案,无需引入与随附代码的耦合程度。尽管我建议更改其中之一的方法名称。乍一看,它看起来像第一种方法中的递归调用。
FireLight

错过了编辑机会。收回有关递归调用的部分,未正确分析代码。重载协同工作效果很好
FireLight

23

很抱歉打败一匹死马,但是没有人指出这一点很奇怪-“是的,但是这与您在现实生活中使用捕获组相反。”

如果以正当方式使用正则表达式,则解决方案就这么简单:

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

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

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

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

通常在想要丢弃的字符串部分上不使用捕获组,在需要保留的字符串部分上使用捕获组。

如果您确实想要替换的组,则可能需要的是模板引擎(例如,小胡子,ejs,StringTemplate等)。


除了好奇之外,即使正则表达式中的非捕获组也只在这种情况下使用,即正则表达式引擎需要它们来识别和跳过可变文本。例如,在

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

如果您的输入看起来像是“ abcabc 捕获我 bcdbcd”或“ abc 捕获我 bcd”,甚至只是“捕获我”,则需要它们。

或换句话说:如果文本始终相同,而您没有捕获它,则根本没有理由使用组。


1
非捕获组是不必要的;\d(.*)\d就足够了。
shmosel

1
我不明白$11这里。为什么是11?
亚历克西斯

1
@Alexis -这是一个java正则表达式怪癖:如果组11没有被设置,JAVA解释$ 11用$ 1,接着1
薯芋

9

通过在周围添加括号来添加第三组.*,然后用替换子序列"number" + m.group(2) + "1"。例如:

String output = m.replaceFirst("number" + m.group(2) + "1");

4
实际上,Matcher支持$ 2样式的引用,因此m.replaceFirst(“ number $ 21”)会做同样的事情。
迈克尔·迈尔斯

实际上,他们不会做相同的事情。 "number$21"有效,"number" + m.group(2) + "1"无效。
艾伦·摩尔

2
看起来number$21将替换组21,而不是组2 +字符串“ 1”。
Fernando M. Pinheiro 2014年

这是纯字符串连接,对吗?为什么我们需要全部调用replaceFirst?
Zxcv Mnb

2

您可以使用matcher.start()和matcher.end()方法来获取组位置。因此,使用此位置可以轻松替换任何文本。


1

替换输入中的密码字段:

{"_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, "**"));
  }

0

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

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();       
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.