使用正则表达式匹配多行文本

我试图使用 java 来匹配多行文本。当我使用带有 Pattern.MULTILINE修饰符的 Pattern类时,我能够匹配,但是我无法匹配 (?m).

同样的模式与 (?m)和使用 String.matches似乎不工作。

我确定我遗漏了一些东西,但不知道是什么。我不太擅长正则表达式。

我试过了

String test = "User Comments: This is \t a\ta \n test \n\n message \n";


String pattern1 = "User Comments: (\\W)*(\\S)*";
Pattern p = Pattern.compile(pattern1, Pattern.MULTILINE);
System.out.println(p.matcher(test).find());  //true


String pattern2 = "(?m)User Comments: (\\W)*(\\S)*";
System.out.println(test.matches(pattern2));  //false - why?
241858 次浏览

str.matches(regex) 表现得像 Pattern.matches(regex, str),它尝试将整个输入序列与模式匹配并返回

如果且仅当 完整的输入序列与此匹配器的模式匹配,则为 true

matcher.find() 试图找到输入序列的下一个子序列匹配模式并返回

当且仅当输入序列的 子序列与此匹配器的模式匹配时

因此问题出在正则表达式上。

String test = "User Comments: This is \t a\ta \ntest\n\n message \n";


String pattern1 = "User Comments: [\\s\\S]*^test$[\\s\\S]*";
Pattern p = Pattern.compile(pattern1, Pattern.MULTILINE);
System.out.println(p.matcher(test).find());  //true


String pattern2 = "(?m)User Comments: [\\s\\S]*^test$[\\s\\S]*";
System.out.println(test.matches(pattern2));  //true

因此,简而言之,第一个正则表达式中的 (\\W)*(\\S)*部分匹配一个空字符串,因为 *意味着零个或多个匹配项,而真正匹配的字符串是 User Comments:,而不是您所期望的整个字符串。第二个失败,因为它试图匹配整个字符串,但是它不能,因为 \\W匹配一个非单词字符,即 [^a-zA-Z0-9_]和第一个字符是 T,一个单词字符。

首先,在不正确的假设下使用修饰符。

Pattern.MULTILINE(?m)告诉 Java 在每行的开始和结束处接受要匹配的锚 ^$(否则它们只在整个字符串的开始/结束处匹配)。

Pattern.DOTALL(?s)告诉 Java 也允许点匹配换行符。

其次,在您的示例中,正则表达式失败是因为您使用了 matches()方法,该方法期望正则表达式与 完整的字符串匹配——当然,这是不可行的,因为在 (\\W)*(\\S)*匹配之后还有一些字符。

因此,如果您只是在寻找以 User Comments:开头的字符串,请使用正则表达式

^\s*User Comments:\s*(.*)

选择 Pattern.DOTALL:

Pattern regex = Pattern.compile("^\\s*User Comments:\\s+(.*)", Pattern.DOTALL);
Matcher regexMatcher = regex.matcher(subjectString);
if (regexMatcher.find()) {
ResultString = regexMatcher.group(1);
}

然后,ResultString将包含 User Comments:之后的文本

这与 MULTILINE 标志无关; 您看到的是 find()matches()方法之间的区别。如果可以找到匹配的 在目标字符串中的任意位置,则 find()成功,而 matches()期望正则表达式匹配 整根弦

Pattern p = Pattern.compile("xyz");


Matcher m = p.matcher("123xyzabc");
System.out.println(m.find());    // true
System.out.println(m.matches()); // false


Matcher m = p.matcher("xyz");
System.out.println(m.matches()); // true

此外,MULTILINE并不意味着你所想的那样。许多人似乎会得出这样的结论: 如果目标字符串包含换行符,那么就必须使用该标志——换句话说,如果它包含多个逻辑行。我已经在 SO 上看到了一些答案,但实际上,标志所做的只是改变锚的行为,即 ^$

通常,^匹配目标字符串的最开始部分,而 $匹配最后部分(或者在最后的换行符之前,但是我们暂时将它放在一边)。但是如果字符串包含换行符,您可以通过设置 MULTILINE 标志来选择 ^$在任何逻辑行的开始和结束处进行匹配,而不仅仅是整个字符串的开始和结束处。

所以忘记什么 MULTILINE 手段,只要记住它 是的: 改变 ^$锚的行为。DOTALL模式最初称为“单行”(仍然有一些风格,包括 Perl 和。NET) ,它总是引起类似的混乱。幸运的是,Java 开发人员在这种情况下使用了更具描述性的名称,但是对于“多行”模式没有合理的替代方案。

在佩尔,所有这些疯狂开始的地方,他们已经承认了自己的错误,并且在 Perl6正则表达式中去掉了“多行”和“单行”模式。再过二十年,也许世界其他地方也会效仿。

多行标志告诉正则表达式将模式匹配到每一行,而对于您的目的来说,一个通配符就足够了。