理解Perl正则表达式修饰符/m和/s

14

我一直在阅读带有s、m和g修饰符的Perl正则表达式。我知道//g是全局匹配,它将进行贪婪搜索。

但是我对s和m修饰符感到困惑。有人能否用代码示例解释s和m之间的区别,并展示它们如何不同?我尝试在网上搜索,只找到了http://perldoc.perl.org/perlre.html#Modifiers中的说明。在stackoverflow上,我甚至看到人们同时使用s和m。难道s不是m的相反吗?

//s 
//m 
//g

使用m时,我无法匹配多行。

use warnings;
use strict;
use 5.012;

my $file; 
{ 
 local $/ = undef; 
 $file = <DATA>; 
};
my @strings = $file =~ /".*"/mg; #returns all except the last string across multiple lines
#/"String"/mg; tried with this as well and returns nothing except String
say for @strings;

__DATA__
"This is string"
"1!=2"
"This is \"string\""
"string1"."string2"
"String"
"S
t
r
i
n
g"
4个回答

18
你自己提供的文档对我来说非常清晰。如果您能解释一下您理解上的问题以及您如何认为/s/m是相反的,那会很有帮助。
简而言之,/s修改了点元字符.的行为,使其匹配任何字符。通常它匹配除换行符"\n"之外的任何字符,并且即使它包含换行符,它也将把字符串视为单行/m修改了插入符号^和美元符号$元字符,使它们在字符串内部匹配换行符,将其视为多行字符串。通常它们只会在字符串的开头和结尾匹配。
您不应该与/g修饰符混淆,因为它是“贪婪”的。它用于全局匹配,将在字符串中找到模式的所有出现。术语“贪婪”通常用于模式内部的限定词行为。例如,.*被称为贪婪,因为它将尽可能匹配尽可能多的字符,而不是.*?,它将匹配尽量的字符。
更新 在您修改后的问题中,您使用了/".*"/mg,其中/m是无关紧要的,因为如上所述,该修饰符仅改变$^元字符的行为,在您的模式中没有这些元字符。
将其更改为/".*"/sg会稍微改善一下,因为.现在可以匹配每行末尾的换行符,因此该模式可以匹配多行字符串。 (请注意,这里考虑的是对象字符串,即匹配只关心是否有换行符,就好像其中没有换行符一样。)然而,这里採取传统的贪婪含义,因为该模式现在匹配从第一行第一个双引号到最后一行最后一个双引号之间的所有内容。我认为这不是您想要的。
有几种方法可以解决这个问题。我建议更改您的模式,使您想要的字符串是双引号,后跟任何字符序列(除了双引号),然后再跟一个双引号。这样写作/"[^"]*"/g(请注意,由于模式中现在没有点,因此不再需要/s修饰符),几乎可以实现您想要的效果,只是转义的双引号被视为结束模式。
看一下这个程序及其输出,注意我在每个匹配项的开头放了一个尖括号>>,以便它们可以区分开来。
use strict;
use warnings;

my $file = do {
  local $/;
  <DATA>; 
};

my @strings = $file =~ /"[^"]*"/g;

print ">> $_\n\n", for @strings;

__DATA__
"This is string"
"1!=2"
"This is \"string\""
"string1"."string2"
"String"
"S
t
r
i
n
g"

输出

>> "This is string"

>> "1!=2"

>> "This is \"

>> ""

>> "string1"

>> "string2"

>> "String"

>> "S
t
r
i
n
g"

正如您所看到的,现在一切都井然有序,除了在"This is \"string\""中它找到了两个匹配项,即"This is \"""。修复这个问题可能比您想象的要复杂,但完全有可能。如果您也需要修复,请告诉我。


更新

我可能会将其完成。为了忽略转义的双引号并将它们视为字符串的一部分,我们需要接受任一\"除双引号外的任何字符。这是使用正则表达式交替运算符|完成的,并且必须在非捕获括号(?: ... )中进行分组。最终结果是/"(?:\\"|[^"])*"/g(反斜杠本身必须转义,因此它被加倍),当放入上面的程序中时,会产生这个输出,我认为这就是您想要的。

>> "This is string"

>> "1!=2"

>> "This is \"string\""

>> "string1"

>> "string2"

>> "String"

>> "S
t
r
i
n
g"

谢谢你的解释。但是,我无法使用m匹配多行。能否请你解释一下编辑后问题中的代码。 - user2763829
正则表达式总是可以匹配多行字符串。m修饰符只是意味着它将字符串识别为多行(用于匹配锚点^$)。但不要过分解读名称“多行”和“单行”。它们是永远不应该存在的模式的糟糕名称。在Perl 6中,它们已被消除。 - Alan Moore
1
那么,当我使用/s时,它会改变.的行为,使得.包含\n作为.的一部分。我的理解正确吗? - user2763829
@user15964: "我花了整个下午在这个网站上提问",但我根本没有看到你关于这个问题的任何提问。不,你不需要选项-0777,它只适用于单行程序。我的解决方案是一个完整的程序,而domy $file = do {local $/; <DATA>; }是读取整个文件的正确方式。OP也使用了相同的结构。我不能为每个可能缺乏知识的人提供解决方案。如果你不知道如何使用$/,那么你需要认真学习一下。 - Borodin
其实,我记得你注意到了我的帖子 http://stackoverflow.com/questions/38561556/cross-line-regex-match-in-a-perl-one-liner :) 所以你的意思是 local $/ 实际上等同于 -0777 对吧? - user15964
显示剩余2条评论

8

/m/s 都会影响匹配操作符对多行字符串的处理。

使用 /m 修饰符,^$ 可以匹配字符串中任意一行的开头和结尾。没有 /m 修饰符时,^$ 只匹配整个字符串的开头和结尾。

例如:

$_ = "foo\nbar\n";

/foo$/,  /^bar/       do not match
/foo$/m, /^bar/m      match

使用/s修饰符,特殊字符.匹配包括换行符在内的所有字符。如果没有/s修饰符,.匹配除换行符之外的所有字符。
$_ = "cat\ndog\ngoldfish";

/cat.*fish/           does not match
/cat.*fish/s          matches

可以同时使用/sm修饰符。

$_ = "100\n101\n102\n103\n104\n105\n";

/^102.*104$/          does not match
/^102.*104$/s         does not match
/^102.*104$/m         does not match
/^102.*104$/sm        matches

4
使用/".*"/mg来匹配:
  1. "开始
  2. 然后.*"尽可能地匹配每个字符(除了\n),直到第一个"
  3. 由于使用了/g并且匹配在第二个"停止,因此正则表达式将尝试重复前两步
  4. /m在这里没有区别,因为您没有使用^$
由于您在示例中转义了引号,因此正则表达式不是执行您想要的操作的最佳工具。 如果不是这种情况,并且您想要获取两个引号之间的所有内容,则可以使用/".*?"/gs

谢谢您的解释。您能给一个代码示例来演示如何使用“/m”吗? - user2763829
@user2763829 https://eval.in/134452 - mpapec

1
然而,反斜杠也有可能被转义。当在字符串中包含Windows路径时,这种情况就会出现,因此以下正则表达式将捕获该情况:
use warnings;
use strict;
use 5.012;

my $file = do { local $/; <DATA>};

my @strings = $file =~ /"(?:(?>[^"\\]+)|\\.)*"/g;

say "<$_>" for @strings;

__DATA__
"This is string"
"1!=2"
"This is \"string\""
"string1"."string2"
"String"
"S
t
r
i
n
g"
"C:\\windows\\style\\path\\"
"another string"

输出:

<"This is string">
<"1!=2">
<"This is \"string\"">
<"string1">
<"string2">
<"String">
<"S
t
r
i
n
g">
<"C:\\windows\\style\\path\\">
<"another string">

快速解释该模式:
my @strings = $file =~ m{
    "
        (?:
            (?>            # Independent subexpression (reduces backtracking)
                [^"\\]+    # Gobble all non double quotes and backslashes
            )
        |
            \\.            # Backslash followed by any character
        )*
    "
    }xg;                   # /x modifier allows whitespace and comments.

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