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

问题描述 投票:0回答:8

我有这段代码,我想知道,是否可以仅替换 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
8个回答
162
投票

使用

$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
    // the added group ("(.*)" which is $2) captures unmodified text to include it in the result
    String output = m.replaceFirst("number$2$1"); // "number example input 6"
}

考虑将

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

编辑

多年后,这个问题仍然得到投票,评论和编辑(打破了答案)表明,人们对这个问题的含义仍然存在困惑。我已经修复了它,并添加了急需的示例输出。

对替换的编辑(有些人认为不应该使用

$2
)实际上打破了答案。尽管持续的投票显示答案击中了关键点 - 在
$n
中使用
replaceFirst(...)
引用来重用捕获的值 - 编辑丢失了这样一个事实:未修改的文本也需要捕获,并在替换中使用,以便“仅组(不是所有模式)”。

这个问题以及这个答案与迭代无关。这是故意的 MRE


76
投票

您可以使用

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, "!!!"));
}

查看在线演示


41
投票

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

如果您按照预期的方式使用正则表达式,解决方案就像这样简单:

"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”,则您需要它们。

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


4
投票

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

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();       
}

4
投票

替换输入中的密码字段:

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

3
投票

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


1
投票

从 Java 9 开始,您可以使用

Matcher.replaceAll
。 使用方法如下:

Pattern p = Pattern.compile("(\\d)(.*)(\\d)");
String input = "6 example input 4";
Matcher matcher = p.matcher(input);
String output = matcher.replaceAll(matchResult -> "%s%s%s".formatted("number", matchResult.group(2), matchResult.group(1) ));

output
应等于
number example input 6

matchResult.group(0)
是整个模式,因此组从
1

开始索引

0
投票

这是一个搜索/替换解决方案,支持:

  1. 带组或不带组的正则表达式。
  2. 仅替换第一个匹配项或所有匹配项。
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Utils {

    public static String searchReplace(String content, 
                                       String matchRegex, 
                                       String replaceText, 
                                       boolean firstOnly) {
        Pattern pattern = Pattern.compile(matchRegex, Pattern.MULTILINE);
        Matcher matcher = pattern.matcher(content);
        int group = matcher.groupCount() > 0 ? 1 : 0;

        StringBuilder output = new StringBuilder();
        while (matcher.find()) {
            Matcher m = pattern.matcher(matcher.group(0));
            if (m.find()) {
                StringBuilder stringBuilder = new StringBuilder(m.group(0));
                String result = stringBuilder.replace(m.start(group), m.end(group), replaceText).toString();
                matcher.appendReplacement(output, result);
            }
            if (firstOnly) {
                break;
            }
        }
        matcher.appendTail(output);
        return output.toString();
    }

}

为了测试所有这些场景,这里有一个单元测试(使用 AssertJ 库:

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

public class UtilsTest {

    @Test
    public void shouldSearchReplace() {
        String input = "black cat, black cat, black dog, white cat";

        // 1. Search with (a) NO GROUPS + (b) REPLACE FIRST ONLY
        assertThat(GeneratorUtils.searchReplace(input, "black", "red", true))
                .isEqualTo("red cat, black cat, black dog, white cat");

        // 2. Search with (a) NO GROUPS + (b) REPLACE ALL
        assertThat(GeneratorUtils.searchReplace(input, "black", "red", false))
                .isEqualTo("red cat, red cat, red dog, white cat");

        // 3. Search with (a) GROUPS + (b) REPLACE FIRST ONLY
        assertThat(GeneratorUtils.searchReplace(input, "black (cat)", "pig", true))
                .isEqualTo("black pig, black cat, black dog, white cat");

        // 4. Search with (a) NO GROUPS + (b) REPLACE ALL
        assertThat(GeneratorUtils.searchReplace(input, "black (cat)", "pig", false))
                .isEqualTo("black pig, black pig, black dog, white cat");
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.