正则表达式和匹配
Perl 操作文本的强大能力部分来源于对一个名为正则表达式的计算概念的囊括。正则 表达式(通常简化为regex或regexp)是一个模式,它描述了某文本字符串的 特征。正则表达式引擎解释一个模式并将其应用到文本字符串上,以识别何者匹配。 Perl 的核心文档丰富而详细地描述了 Perl 的正则表达式;请参考 字面值最简单的正则表达式就是简单的子字符串模式: my $name = 'Chatfield'; say "Found a hat!" if $name =~ /hat/; 匹配操作符( 绑定操作符的否定形式( qr// 操作符和正则表达式组合在现代化的 Perl 中,当由qr// 操作符创建时,正则表达式是第一级实体:
my $hat = qr/hat/; say 'Found a hat!' if $name =~ /$hat/; my $hat = qr/hat/; my $field = qr/field/; say 'Found a hat in a field!' if $name =~ /$hat$field/; # 或 like( $name,qr/$hat$field/,'Found a hat in a field!' ); 量词正则表达式比前例演示的更为强大;你可以使用index 操作符在字符串内搜索 子字符串字面值。但用正则表达式引擎达成这项任务就像驾驶自治区战斗直升机去 拐角小店购买备用奶酪。
通过使用正则表达式量词,正则表达式可以变得更为强大,使你能够指定一个正则表达式 组件在匹配字符串中出现的频率。最简单的量词是零个或一个量词,或者说? :
my $cat_or_ct = qr/ca?t/; like( 'cat',$cat_or_ct,"'cat' matches /ca?t/" ); like( 'ct',"'ct' matches /ca?t/" );在正则表达式中,任意原子后接 ? 字符意味着“匹配此原子零次或一次”。这个正则表 达式匹配c 后立即跟随零个a 字符再接一个t 字符。它同时也匹配在c 和t 字符间有一个a 。
一个或多个量词,或者说+ ,在字符串中只匹配量词前原子至少出现一次的情况:
my $one_or_more_a = qr/ca+t/; like( 'cat',$one_or_more_a,"'cat' matches /ca+t/" ); like( 'caat',"'caat' matches /ca+t/" ); like( 'caaat',"'caaat' matches /ca+t/" ); like( 'caaaat',"'caaaat' matches /ca+t/" ); unlike( 'ct',"'ct' does not match /ca+t/" );能匹配多少原子没有什么理论上的限制。 零个或多个量词是 * ;它匹配量化原子在字符串中出现的零个或多个实例:
my $zero_or_more_a = qr/ca*t/; like( 'cat',$zero_or_more_a,"'cat' matches /ca*t/" ); like( 'caat',"'caat' matches /ca*t/" ); like( 'caaat',"'caaat' matches /ca*t/" ); like( 'caaaat',"'caaaat' matches /ca*t/" ); like( 'ct',"'ct' matches /ca*t/" );这看上去可能不很有用,但是它和其他正则表达式功能可以组合得很好,让你不必关心在 特定位置是否出现某模式。即便如此,大多数正则表达式从使用 ? 和+ 量词 中获益远多于* 量词,因为它们可以避免昂贵的回溯,并将你的意图表达得更为清晰。
最后,你可以通过数值量词指定某原子匹配的次数。{n} 意味着确切匹配n次。
# equivalent to qr/cat/; my $only_one_a = qr/ca{1}t/; like( 'cat',$only_one_a,"'cat' matches /ca{1}t/" ); {n,} 意味着匹配次数必须至少为n次,同时可以匹配更多次数:
# equivalent to qr/ca+t/; my $at_least_one_a = qr/ca{1,}t/; like( 'cat',$at_least_one_a,"'cat' matches /ca{1,}t/" ); like( 'caat',"'caat' matches /ca{1,}t/" ); like( 'caaat',"'caaat' matches /ca{1,}t/" ); like( 'caaaat',"'caaaat' matches /ca{1,}t/" ); my $one_to_three_a = qr/ca{1,3}t/; like( 'cat',$one_to_three_a,3}t/" ); like( 'caat',3}t/" ); like( 'caaat',3}t/" ); unlike( 'caaaat',"'caaaat' does not match /ca{1,3}t/" ); 贪心性就+ 和* 自身来说,它们是贪心量词;它们尽可能多地匹配输入字符串。在利 用.* 来匹配“任何数量的任何字符”时尤其有害:
# a poor regex my $hot_meal = qr/hot.*meal/; say 'Found a hot meal!' if 'I have a hot meal' =~ $hot_meal; say 'Found a hot meal!' if 'I did some one-shot,piecemeal work!' =~ $hot_meal;贪心量词总是试图先行匹配尽可能多的输入字符串,仅在匹配明显不成功时回退。如 果你用如下方式查找单词“loam(土壤)”,你将无法把所有结果塞进 7 Down 的四个盒子 里面: my $seven_down = qr/l${letters_only}*m/;作为新手,你将得到 Alabama 、Belgium 以及Bethlehem 。那里的土壤也许不错, 但是它们全都太长了————并且,匹配是从单词的中间开始的。
让贪心量词变成非贪心量词只需在其后加上? 量词:
my $minimal_greedy_match = qr/hot.*?meal/;当给予非贪心量词,正则表达式引擎将偏向最短的、可能的潜在匹配,并仅在目前 数目无法满足匹配要求时,增加由 .*? 标记组合识别的字符数量。由于* 匹配 零或更多次,对应这个标记组合的最小潜在匹配是零个字符:
say 'Found a hot meal' if 'ilikeahotmeal' =~ /$minimal_greedy_match/;使用 + 量词可以匹配某项一次或多次:
my $minimal_greedy_at_least_one = qr/hot.+?meal/; unlike( 'ilikeahotmeal',$minimal_greedy_at_least_one ); like( 'i like a hot meal',$minimal_greedy_at_least_one );?量词修饰符也可以应用于 ? (零或一次匹配)和范围量词。在每种情况下,它使 得正则表达式尽可能地少匹配。
贪心修饰符.+ 和.* 是诱人但危险的。如果你编写了贪心匹配正则表达式,请用 综合自动化测试套件和代表性数据完整地测试它,以将产生令人不快结果的可能性降到最 小。
正则表达式锚点正则表达式锚点强制在字符串某位置进行匹配。字符串开头锚点(A )确保任 何匹配都将从字符串开头开始:
# 也匹配 "lammed"、"lawmaker" 和 "layman" my $seven_down = qr/Al${letters_only}{2}m/;字符串末尾锚点( Z )确保任何匹配都将结束于字符串末尾:
# 也匹配 "loom",它足够接近 my $seven_down = qr/Al${letters_only}{2}mZ/;单词边界元字符( b )仅匹配一单词字符 (w ) 和另一非单词字符 (W ) 之间的边界。因此,查找loam 而非Belgium ,可以使用加锚点的正则表达式:
my $seven_down = qr/bl${letters_only}{2}mb/; 与 Perl 类似,达成某个目的的正则表达式也有许多写法。请考虑从中挑出最有表达力 也是最易维护的一个。 元字符正则表达式随着原子的一般化而变得更为强大。举例来说,在正则表达式内,. 字符 的意思是“匹配除换行外的任意字符”。玩填字游戏时,如果你想在一个单词列表里查找每 一个匹配的 7 Down(“Rich soil”),你可以这样写:
for my $word (@words) { next unless length( $word ) == 4; next unless $word =~ /l..m/; say "Possibility: $word"; }当然,如果你的候选匹配列表由单词外的东西构成,这个元字符可能导致假阳性,因为它同时 匹配标点符号、空格、数字以及其他的非单词字符。 w 元字符代表所有字母数字符(按 Unicode 处理————Unicode and Strings)还有下划线:
next unless $word =~ /lwwm/;d元字符匹配数字————不是你预期的 0-9,而是 Unicode 数字: # 并非一个健壮的电话号码匹配器 next unless $potential_phone_number =~ /d{3}-d{3}-d{4}/; say "I have your number: $potential_phone_number";可以使用 s 元字符匹配空白,无论是字面空格、制表符、硬回车、换页符或者换行:
my $two_three_letter_words = qr/w{3}sw{3}/;这些元字符也有否定形式。要匹配除单词外的其他字符,使用 W 。要匹配非数字 字符,使用D 。要匹配非空白字符,使用S 。
正则表达式引擎将所有元字符作为原子对待。
字符类如果允许字符的范围在上述四组里不够具体,通过把它们用中括号围起,你可以自行指定字符类:my $vowels = qr/[aeiou]/; my $maybe_cat = qr/c${vowels}t/; 标量变量名 - )作为表达该范围的 快捷方式。
my $letters_only = qr/[a-zA-Z]/;将连字符添加到字符类的开头或结尾可以将其包括进此字符类中: my $interesting_punctuation = qr/[-!?]/;……或对其进行转义: my $line_characters = qr/[|=-_]/;就像单词和数字类元字符( w 和d )有自己的否定形式,你也可以否定一个字 符类。用插入符号(^ )作为字符类的第一个元素意味着“除这些外的所有字符”:
my $not_a_vowel = qr/[^aeiou]/; 在此之外使用插入符号使其成为该字符类的一个成员。要在否定字符类里包含一个连字符, 可以将它放在插入符号后,或者在字符类的最后,再或者对其转义。 捕获通常的做法是先匹配字符串的一部分并在稍后对其进行处理;也许你想从一个字符串中提取 一个地址或美国电话号码:my $area_code = qr/(d{3})/; my $local_number = qr/d{3}-?d{4}/; my $phone_number = qr/$area_codes?$local_number/; 正则表达式中的括号是元字符; 具名捕获给出一个字符串, if ($contact_info =~ /(?<phone>$phone_number)/) { say "Found a number $+{phone}"; } 捕捉结构可能看上去像是一大个摇晃的标点,当你可以将其作为整体认读时,它还是比较 简单的: (?<capture name> ... ) 括号包围了整个捕获。 对于 Perl 5 正则表达式来说,括号是特殊的;默认和常规的 Perl 代码一样,它们的行为 就是进行分组。它们也将匹配部分组成的一个或多个原子包围在内。要在正则表达式内使用 字面括号,你必须添加反斜杠,就像 编号捕获具名捕获是 Perl 5.10 的新功能,但捕获早已在 Perl 中存在了许多年头。你也会碰到编号捕获: if ($contact_info =~ /($phone_number)/) { say "Found a number $1"; } 括号把要捕获片段包围在内,但是没有正则表达式元字符给出捕获的名称。作为代替, Perl 将捕获的子字符串存放在一系列以 虽然具名捕获的语法比编号捕获来得长一些,但它提供了额外的清晰度。你不需要统计开 括号的的个数来指出某捕获会被存入 当你将一处匹配在列表上下文中求值时,编号捕获相对不那么令人沮丧: if (my ($number) = $contact_info =~ /($phone_number)/) { say "Found a number $number"; } Perl 将按捕获顺序赋值给左值: 成组和选项前面的例子将全部量词应用于简单原子上。它们也可以应用于一个更为复杂的子模式整体: my $pork = qr/pork/; my $beans = qr/beans/; like( 'pork and beans',qr/A$pork?.*?$beans/,'maybe pork,definitely beans' ); 如果手动扩展该正则表达式,结果可能令你感到惊讶: like( 'pork and beans',qr/Apork?.*?beans/,definitely beans' ); 这样仍然匹配,但考虑一个更为具体的模式: my $pork = qr/pork/; my $and = qr/and/; my $beans = qr/beans/; like( 'pork and beans',qr/A$pork? $and? $beans/,maybe and,definitely beans' ); 一些正则表达式不是匹配这项就是匹配另一项。使用选项元字符 ( my $rice = qr/rice/; my $beans = qr/beans/; like( 'rice',qr/$rice|$beans/,'Found some rice' ); like( 'beans','Found some beans' ); 选项元字符意味着匹配前述任一片段。但请注意解释为正则表达式片段的内容: like( 'rice',qr/rice|beans/,'Found some rice' ); like( 'beans','Found some beans' ); unlike( 'ricb','Found some weird hybrid' ); 模式 为了减少迷惑性,可以像变量 ( my $starches = qr/(?:pasta|potatoes|rice)/;
其他转义序列Perl 将正则表达式内的若干字符解释为元字符,它们代表不同于他们字面形式的意义。 中括号总是标示一个字符类,括号则将片段成组且可选地进行捕获。 要匹配一个元字符的字面实例,可以用反斜杠( 其他通常需要转义的有用的元字符是管道符( 为避免处处转义(和担心忘记转义内插的值),可以使用元字符禁用字符。 my ($text,$literal_text) = @_; return $text =~ /Q$literal_textE/;
在处理来自不可信任的用户输入时须特别小心。构造一个恶意正则表达式对你的程序进行 有效的拒绝服务攻击是完全可以办到的。 断言正则表达式锚点( 零宽度断言匹配一个模式,不仅仅是一个字符串中的条件。最重要的是,它们不消耗 它们匹配模式中的位置。例如,你只要找一只“cat(猫)”,你可以是用单词边界断言: my $just_a_cat = qr/catb/; ……但如果想找一非灾难性的“cat”,你也许会用到零宽度否定前瞻断言: my $safe_feline = qr/cat(?!astrophe)/;
零宽度否定前瞻断言: my $disastrous_feline = qr/cat(?=astrophe)/; ……仅在短语 my $disastrous_feline = qr/cat(?!astrophe)/; while (<$words>) { chomp; next unless /A(?<some_cat>$disastrous_feline.*)Z/; say "Found a non-catastrophe '$+{some_cat}'"; } 因为断言宽度为零,它不消耗源字符串。因此,带锚点的 零宽度后顾断言也是存在的。不像前瞻断言那样,这些断言的模式的长度必须固定;你不可 以在这些模式中使用量词。 要对你的猫绝不会出现在行首做出断言,你可以使用零宽度否定后顾断言: my $middle_cat = qr/(?<!^>cat/; ……此处的
my $space_cat = qr/(?<=s)cat/; ……此处的 正则表达式修饰符正则表达式操作符允许若干修饰符改变匹配的行为。这些修饰符出现在匹配、替换和 my $pet = 'CaMeLiA'; like( $pet,qr/Camelia/,'You have a nice butterfly there' ); like( $pet,qr/Camelia/i,'Your butterfly has a broken shift key' ); 第一个 你也可以在模式中内嵌修饰符: my $find_a_cat = qr/(?<feline>(?i)cat)/;
my $find_a_rational = qr/(?<number>(?-i)Rat)/; 多行操作符,
my $attr_re = qr{ ^ # 行首 # 杂项 (?: [;ns]* # 空白和伪分号 (?:/*.*?*/)? # C 注释 )* # 属性标记 ATTR # 类型 s+ ( U?INTVAL | FLOATVAL | STRINGs+* | PMCs+* | w* ) }x; 这个正则表达式不简单,但注释和空白提高了它的可读性。即便你利用已编译的片段一起 编写正则表达式,
# appease the Mitchell estate my $contents = slurp( $file ); $contents =~ s/Scarlett O'Hara/Mauve Midway/g; 当和匹配一起使用时────并非替换──── while ($contents =~ /G(w{3})(w{3})(w{4})/g) { push @numbers,"($1) $2-$3"; } 注意
# appease the Mitchell estate my $contents = slurp( $file ); $contents =~ s{Scarlett( O'Hara)?} { 'Mauve' . defined $1 ? ' Midway' : '' }ge; 你可以向一次替换操作添加任意多的 智能匹配
智能匹配操作符, 智能匹配操作符是一个中缀操作符: say 'They match (somehow)' if $loperand ~~ $roperand; 比较的类型大致先由右操作符的类型决定然后再是左操作符。例如,如果右操作符是一个 带数值成分的标量,则比较将使用数值等于。如果右操作符是一个正则表达式,则比较将 是一个 grep 操作或模式匹配。如果右操作符是一个数组,比较将是 grep 操作或递归的 智能匹配。如果右操作符是一个哈希,比较操作将检查一个或多个键是否存在。 例如: # 标量数值比较 my $x = 10; my $y = 20; say 'Not equal numerically' unless $x ~~ $y; # 标量类数值比较 my $x = 10; my $y = '10 little endians'; say 'Equal numeric-ishally' if $x ~~ $y; ……以及: my $needlepat = qr/needle/; say 'Pattern match' if $needle ~~ $needlepat; say 'Grep through array' if @haystack ~~ $needlepat; say 'Grep through hash keys' if %hayhash ~~ $needlepat; ……再及: say 'Grep through array' if $needlepat ~~ @haystack; say 'Array elements exist as hash keys' if %hayhash ~~ @haystack; say 'Array elements smart match' if @strawstack ~~ @haystack; ……又及: say 'Grep through hash keys' if $needlepat ~~ %hayhash; say 'Array elements exist as hash keys' if @haystack ~~ %hayhach; say 'Hash keys identical' if %hayhash ~~ %haymap; 这些比较操作在某操作数是给出数据类型的引用时也能正常工作。举例来说: say 'Hash keys identical' if %hayhash ~~ %hayhash; 你可以在对象上重载(é??è??)智能匹配操作符。如果你不这样做,智能匹配 操作符在你尝试将某对象用作操作数时会抛出异常。 你也可以使用如 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |