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

201

我正在尝试使用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?
4个回答

357

首先,您正在基于错误的假设使用修改器。

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:之后的文本。


3
иҝҷдёӘжңүж•ҲпјҲи°ўи°ўпјҒпјүгҖӮжҲ‘е°қиҜ•дәҶжЁЎејҸ(?s)User Comments:\s*(.*)гҖӮд»Һ@Amarghoshзҡ„еӣһзӯ”дёӯпјҢжҲ‘еҫ—еҲ°дәҶжЁЎејҸUser Comments: [\\s\\S]*гҖӮиҝҷдёӨз§Қж–№жі•жҳҜеҗҰжңүжӣҙеҘҪжҲ–жҺЁиҚҗзҡ„ж–№ејҸпјҢиҝҳжҳҜеҸӘжҳҜдёӨз§ҚдёҚеҗҢзҡ„еҒҡжі•пјҹ - Nivas
3
它们的意思是相同的;[\s\S] 更加明确("匹配任何空格或非空格字符"),. 更易读,但您需要查找 (?s)DOTALL 修改符以确定是否包括换行符。我更喜欢使用带有 Pattern.DOTALL 标志的 .(在我看来,这比 (?s) 更易于阅读和记忆)。您应该使用您感到最舒适的方式。 - Tim Pietzcker
使用DOTALL.*更易读。我使用另一个来表明问题在于str.matches和matcher.find之间的差异,而不是标志。+1 - Amarghosh
DOTALL 救了我。谢谢。 - WesternGun
我正在使用@Pattern注解在变量上匹配正则表达式模式:[a-zA-Z0-9][0-9a-zA-Z,/. -] 请问我该如何允许多行字符串? - Aakash Patel
显示剩余3条评论

50

这与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 并不意味着你认为的那样。许多人似乎会得出这样的结论:如果目标字符串包含换行符(即包含多个逻辑行),则必须使用该标志。我在这里看到过几个答案是这么说的,但实际上该标志所做的就是改变锚点 ^$ 的行为。
通常情况下,^ 匹配目标字符串的开头,$ 匹配结尾(或结尾前的换行符,但我们暂且不谈)。但如果字符串包含换行符,则可以选择让 ^$ 匹配任何逻辑行的开头和结尾,而不仅仅是整个字符串的开头和结尾,方法是设置 MULTILINE 标志。
因此,请忘记 MULTILINE 的含义,只记住它的作用:更改 ^$ 锚点的行为。DOTALL 模式最初称为“单行”(在某些语言中仍然如此,包括 Perl 和 .NET),并且一直引起类似的混淆。我们很幸运,Java 开发人员在那种情况下选择了更具描述性的名称,但对于“多行”模式没有合理的替代方法。
在 Perl 中,这种疯狂的做法开始后,他们承认了自己的错误,并在 Perl 6 正则表达式中摆脱了“多行”和“单行”模式。也许再过二十年,整个世界都会效仿他们。

6
难以置信他们使用“#matches”这个方法名来表示“全部匹配”,吓人。 - rogerdpack
@alan-moore 抱歉,尽管它是正确的,但我还是点了下降 [需要更多的睡眠:)] - Raymond Naseef

23

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

true仅当完整的输入序列与此匹配器的模式相匹配时

matcher.find() 尝试查找下一个与模式匹配的输入序列子序列,并返回

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

因此问题出在正则表达式上。请尝试以下内容。

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,所以无法匹配成功。

我想匹配任何以“用户评论”开头的字符串,该字符串也可以包含换行符。因此,我使用了模式User Comments: [\\s\\S]*,这起作用了(谢谢!)。从@Tim的答案中,我得到了模式User Comments:(.*),这也可以。现在,这两种方法中是否有推荐更好的方法,还是这只是两种做同样事情的方式? - Nivas
@Nivas,就性能而言,我认为两者没有区别;但我认为在使用DOTALL标志的情况下,(.*)更明显/可读,胜过于([\\s\\S]*) - Amarghosh
这是最佳答案……它提供了访问 Java 代码和模式字符串选项,以实现多行功能。 - GoldBishop

1
多行标志告诉正则表达式将模式与每行匹配,而不是整个字符串。对于您的目的,通配符就足够了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接