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


174

我正在尝试使用Java匹配多行文本。当我将Pattern类与Pattern.MULTILINE修饰符一起使用时,我可以匹配,但不能(?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?

Answers:


298

首先,您在错误的假设下使用修饰符。

Pattern.MULTILINE(?m)告诉Java接受锚点^$在每行的开头和结尾进行匹配(否则,它们仅在整个字符串的开头/结尾进行匹配)。

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

其次,在您的情况下,正则表达式失败,因为您使用的matches()是期望正则表达式匹配整个字符串的方法-这当然不起作用,因为(\\W)*(\\S)*匹配后还剩下一些字符。

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

^\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:


我试图找到一种模式,该模式将匹配以“ User Comments:”开头的任何字符串。在此“用户评论:”之后,是用户在文本区域中输入的内容,因此可以包含任何内容,甚至包括新行。看来我需要在正则表达式中学习很多...
Nivas

2
这有效(谢谢!)我尝试了该模式(?s)User Comments:\s*(.*)。从@Amarghosh的答案中我得到了模式User Comments: [\\s\\S]*。在这些方法中,有没有更好的方法推荐的方法,或者这只是两种不同的相同方法?
Nivas 2010年

3
他们的意思是一样的。[\s\S]更加明确(“匹配空白或非空白的任何字符”),.更易于阅读,但是您需要查找(?s)DOTALL修饰符才能确定是否包含换行符。我更喜欢.使用Pattern.DOTALL标志集(比(?s)我认为这更容易阅读和记住。您应该使用自己觉得最舒服的方式。)
Tim Pietzcker 2010年

.*DOTALL更具可读性。我用另一个来表明问题在于str.matches和matcher.find之间的差异,而不是标志。+1
Amarghosh 2010年

我更喜欢.*Pattern.DOTALL,但是必须与(?s)一起使用,因为我必须使用String.matches
Nivas

42

这与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开发人员使用了更具描述性的名称,但是“多行”模式没有合理的替代方法。

在所有疯狂开始的Perl中,他们已经承认了自己的错误,并摆脱了Perl 6正则表达式中的“多行”和“单行”模式。再过二十年,也许世界其他地区也将效仿。


5
难以置信,他们使用方法名称“ #matches”来表示“匹配所有” yike
rogerdpack 2012年

@ alan-moore对不起,即使它是正确的,我也对此表示
反对

22

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 2010年

@Nivas我认为在性能上不会有任何差异;但我认为(.*)随着DOTALL标志更为明显可读/比([\\s\\S]*)
Amarghosh

这是最好的答案。...同时提供对Java代码和Pattern String选项的访问,以实现MultiLine功能。
GoldBishop

0

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

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.