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

Posted

技术标签:

【中文标题】使用正则表达式匹配多行文本【英文标题】:Match multiline text using regular expression 【发布时间】:2011-04-08 18:43:25 【问题描述】:

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

【问题讨论】:

【参考方案1】:

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

true 当且仅当整个输入序列匹配此匹配器的模式

matcher.find() attempts to 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 我认为在性能方面不会有任何差异;但我认为(.*)DOTALL 标志比([\\s\\S]*) 更明显/可读 这是最好的答案....提供对 Java 代码和模式字符串选项的访问,以实现 MultiLine 功能。【参考方案2】:

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

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

【讨论】:

我正在尝试找到一个匹配任何以“用户评论:”开头的字符串的模式。在此“用户评论:”之后是用户在文本区域中输入的内容,因此可以包含 anything - 甚至是新行。看来我需要在正则表达式中学到很多东西...... 这行得通(谢谢!)我尝试了模式 (?s)User Comments:\s*(.*) 。从@Amarghosh 的回答中,我得到了模式User Comments: [\\s\\S]*。其中有一个更好的推荐的方式,还是这只是两种不同的方式? 两者意思相同; [\s\S] 更明确一点(“匹配任何空白或非空白字符”),. 更易于阅读,但您需要查找 (?s)DOTALL 修饰符才能找出是否包含换行符。我更喜欢带有Pattern.DOTALL 标志的.(在我看来,这比(?s) 更容易阅读和记住。你应该使用你觉得最舒服的东西。 .*DOTALL 更具可读性。我用另一个来表明问题在于 str.matches 和 matcher.find 之间的差异,而不是标志。 +1 我更喜欢 .*Pattern.DOTALL,但必须使用 (?s),因为我必须使用 String.matches【参考方案3】:

这与 MULTILINE 标志无关;您看到的是 find()matches() 方法之间的区别。 find() 如果可以在目标字符串中的任意位置找到匹配项,则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 正则表达式中的“多行”和“单行”模式。再过二十年,也许世界其他地方也会效仿。

【讨论】:

很难相信他们使用方法名称“#matches”来表示“匹配所有”耶 @alan-moore 抱歉,尽管它是正确的 [需要更多睡眠 :)]【参考方案4】:

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

【讨论】:

以上是关于使用正则表达式匹配多行文本的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式如何匹配多行的所有任意字符

Vim 多行正则表达式给出重叠匹配

Vim:当使用 \_ 匹配多行字符串时。在正则表达式中, :yank 命令仅适用于第一行

Python正则表达式,多行匹配模式..为啥这不起作用?

JavaScript 正则表达式详细分析

javascript 正则表达式的使用