第六章 正则表达式

作者: 可以没名字吗 | 来源:发表于2016-03-01 14:41 被阅读424次

    Perl大部分的文本处理能力来自它所使用的正则表达式。
    所谓正则表达式就是一个模式 ,这个模式描述了一段文本所具有的特征。正则表达式引擎就应用这些模式来匹配和替换文本。

    Perl中相关的正则表达式文档有:

    perldoc perlretut 入门教程
    perldoc perlreref 参考指南
    perldoc perlre 有关正则的完整文档

    字面量

    一个正则表达式可以简单到就是一些字符:

    my $name = 'Chatfield';
    say 'Found a hat!' if $name =~ /hat/;
    

    匹配操作符(m//, 简写为 //)标识着这是一个正则表达式。
    这个正则表达式表示了这样的一个模式特征:字符h 后面跟着一个字符a,再后面是个字符t。

    正则表达式的绑定操作符(=~ )是一个中缀操作符,前面是要测试的字符串,后面是正则表达式(模式)。
    在标量语境中,若匹配则返回真值,不匹配就返回假。绑定操作符的否定形式(!~)求值规则相反:匹配返回假值,不匹配返回真值。

    替换操作符(s///),某种意义上来说是个环缀操作符,它有2个操作数,第一个操作数是正则表达式,第二个操作数是将匹配的部分替换成什么。使用绑定操作符来绑定操作对象。

    my $status = 'I feel ill.';
    $status =~ s/ill/well/;
    say $status;
    
    #I feel well.
    

    qr//操作符和正则表达式组合

    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!' );
    #Test::More 的like()函数,第一个参数是要测试的目标,第二个参数是正则表达式模式。
    

    量词

    使用量词可以使正则表达式威力大增。量词用来表示字符出现的频率。

    ?表示0次或1次:

    my $cat_or_ct = qr/ca?t/;    
    #ct匹配,cat匹配,caat不匹配
    
    like( 'cat', $cat_or_ct, "'cat' matches /ca?t/" );
    like( 'ct', $cat_or_ct, "'ct' matches /ca?t/" );
    

    +表示一次或多次(也就是至少一次):

    my $some_a = qr/ca+t/;
    
    like( 'cat', $some_a, "'cat' matches /ca+t/" );
    like( 'caat', $some_a, "'caat' matches/" );
    like( 'caaat', $some_a, "'caaat' matches" );
    like( 'caaaat', $some_a, "'caaaat' matches" );
    unlike( 'ct', $some_a, "'ct' does not match" );
    

    *表示任意次,0次、1次、多次均匹配:

    my $any_a = qr/ca*t/;
    
    like( 'cat', $any_a, "'cat' matches /ca*t/" );
    like( 'caat', $any_a, "'caat' matches" );
    like( 'caaat', $any_a, "'caaat' matches" );
    like( 'caaaat', $any_a, "'caaaat' matches" );
    like( 'ct', $any_a, "'ct' matches" );
    
    #量词*的使用需要留心,容易出错。
    

    指定明确次数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 $some_a = qr/ca{1,}t/;
    
    like( 'cat', $some_a, "'cat' matches /ca{1,}t/" );
    like( 'caat', $some_a, "'caat' matches" );
    like( 'caaat', $some_a, "'caaat' matches" );
    like( 'caaaat', $some_a, "'caaaat' matches" );
    

    {n,m}不少于n次,不大于m次

    my $few_a = qr/ca{1,3}t/;
    
    like( 'cat', $few_a, "'cat' matches /ca{1,3}t/" );
    like( 'caat', $few_a, "'caat' matches" );
    like( 'caaat', $few_a, "'caaat' matches" );
    unlike( 'caaaat', $few_a, "'caaaat' doesn't match" );
    

    贪婪

    量词+ 和*具有贪婪的特性,默认它们会尽可能多去匹配。

    # 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 'one-shot, piecemeal work!' =~ $hot_meal;
    

    使用?来转化为非贪婪的,非贪婪量词会尽量短的匹配。

    my $minimal_greedy = qr/hot.*?meal/;
    

    锚位

    锚位就是做位置上的控制。
    字符串开始(\A)

    # also matches "lammed", "lawmaker", and "layman"
    my $seven_down = qr/\Al${letters_only}{2}m/;
    
    end of line string anchor (\z)
    # also matches "loom", but an obvious improvement
    my $seven_down = qr/\Al${letters_only}{2}m\z/;
    

    你还可能见过用^ 和 $用来表示字符串开始和结束位置。^其实是匹配换行符后面,同样的$匹配的是换行符前面。\A 和\z更具体,建议使用这种方式。

    字符边界(\b),匹配的是字符(\w)和非字符(\W)之间的那个位置。边界不是一个字符,它0宽度,不可见。

    my $seven_down = qr/\bl${letters_only}{2}m\b/;
    

    元字符

    正则表达式中,元字符代表的不是字面本身的意思,而是解释出来的意思。我们已经见过元字符了,比如斜杠(\)和量词(?)。
    一些元字符:

    .  代表换行符之外的一切东西
    \w  代表所有数字,字符和下划线。
    \d  代表所有数字
    \s  代表空白:空格,制表符,回车,换页,换行符
    
    

    一般使用大写来表示相反的意思。

    \ W匹配任何字符,除了数字,字符和下划线
    \ D匹配一个非数字字符
    \ S匹配任何东西,但空白除外
    \ B匹配任何位置除了字符边界
    

    字符集

    你可以使用方括号来组织需要的候选项:

    my $ascii_vowels = qr/[aeiou]/;
    my $maybe_cat = qr/c${ascii_vowels}t/;
    

    方括号中还允许使用连字符(-)来包含连续的范围:

    my $ascii_letters_only = qr/[a-zA-Z]/;
    

    如果要将连字符作为候选的成员,可以将它放在最前面或最后面或进行转义:

    my $interesting_punctuation = qr/[-!?]/;
    my $line_characters = qr/[|=\-_]/;
    

    在最前面放置^表示取反:

    my $not_an_ascii_vowel = qr/[^aeiou]/;
    
    #如果想要把它是作为候选的成员,那就放在其他位置,或者直接转义它。
    

    捕获

    正则表达式允许你使用括号来捕获匹配的部分,以备过后使用。

    #假设电话号码为:(202)456-1111
    my $area_code = qr/\(\d{3}\)/;
    my $local_number = qr/\d{3}-?\d{4}/;
    my $phone_number = qr/$area_code\s?$local_number/;
    
    #注意第一行的括号需要转义,因为需要原样的放到更复杂的正则表达式中。
    
    以左括号的次序,依次将匹配的结果存入变量$1,$2,$3....
    
    #捕获
    if ($contact_info =~ /($phone_number)/)
    {
    say "Found a number $1";
    }
    

    在列表语境中,返回的是捕获结果。

    给捕获命名

    我们可以给要捕获的组增加名字来更好的识别它:

    if ($contact_info =~ /(?<phone>$phone_number)/)
    {
    say "Found a number $+{phone}";
    }
    

    命名捕获的语法:

    (?<capture name> ... )
    

    匹配成功时,以名字为键,匹配的部分为值存入到“%+”这个哈希中。

    分组

    my $pork = qr/pork/;
    my $beans = qr/beans/;
    
    like( 'pork and beans', qr/\A$pork?.*?$beans/, 'maybe pork, definitely beans' );
    

    如果你自己手动展开这个正则表达式,结果可能不是你期望的:

    my $pork_and_beans = qr/\Apork?.*beans/;
    
    like( 'pork and beans', qr/$pork_and_beans/, 'maybe pork, definitely beans' );
    like( 'por and beans', qr/$pork_and_beans/, 'wait... no phylloquinone here!' );
    #均会匹配
    

    来我们把模式写得更精确些:

    my $pork = qr/pork/;
    my $and = qr/and/;
    my $beans = qr/beans/;
    
    like( 'pork and beans', qr/\A$pork? $and? $beans/, 'maybe pork, maybe and, definitely beans' );
    

    可选操作符(|):

    my $rice = qr/rice/;
    my $beans = qr/beans/;
    
    like( 'rice', qr/$rice|$beans/, 'Found rice' );
    like( 'beans', qr/$rice|$beans/, 'Found beans' );
    

    这样写容易产生疑惑:

    like( 'rice', qr/rice|beans/, 'Found rice' );
    like( 'beans', qr/rice|beans/, 'Found beans' );
    unlike( 'ricb', qr/rice|beans/, 'Found hybrid' );
    

    但是实际上由于| 的操作符优先级很低,意思是一样的,不过还是使用变量比较清楚。

    括号默认会启用捕获,但是可以指定为不捕获。

    my $starches = qr/(?:pasta|potatoes|rice)/;
    #不捕获,括号只是用来分组。
    

    其他转义序列

    对于元字符,要匹配它们的字面意思就需要转义。

    匹配左括号 \( 
    匹配字符点 \. 
    这些字符也要转义才能表示它们的字面意思 |,$,+,?,* 
    

    还有一个方法就是使用元字符禁用字符(\Q \E)来禁用元字符特性。当模式来源于外部(你无法控制)时,这个特性非常有用:

    my ($text, $literal_text) = @_;
    return $text =~ /\Q$literal_text\E/;
    
    #此时$literal_text里面的字符将没有元字符特性,任何字符都是字面意义。
    #比如是字符串 .*A就等同\.\*A
    #表示一个点号一个*号一个A。
    

    处理用户输入的模式时要小心,一个恶意的正则表达式可能需要花费超常时间来匹配,从而导致拒绝服务攻击。

    断言

    正则表达式中的锚位如\A \b \B \Z就是断言,断言的作用是要求字符串符合一定的条件,本身并不匹配任何字符(所以又称零宽断言)。无论字符串的内容是什么,正则表达式qr/\A/总是会匹配成功。

    这是一个很重要的特性:零宽断言不会消费字符串,它只是一种要求模式。比如你想要找到单词cat,就可以用边界断言(锚位):

    my $just_a_cat = qr/cat\b/;
    

    你也可以要求cat后面不能是字符串astrophe,这时就可以使用零宽否定前向断言(?!...)语法:

    my $safe_feline = qr/cat(?!astrophe)/;
    

    对应的还有零宽肯定前向断言(?=...):

    my $disastrous_feline = qr/cat(?=astrophe)/;
    #cat后面必须是字符串astrophe才能匹配。
    

    还有向后的零宽断言,零宽否定后向断言(?<!...):

    my $middle_cat = qr/(?<!\A)cat/;
    #不能一开始就是cat
    

    零宽肯定后向断言(?<=...):

    my $space_cat = qr/(?<=\s)cat/;
    #cat前面必须是空白字符
    

    Perl 正则表达式还有个功能:保持断言(\K),它的作用是用来保持匹配的状态,但是\K左边的字符不会被放在匹配中的部分。(其实相当于一个后向断言)
    有些情况下非常有用:

    #替换一部分
    my $exclamation = 'This is a catastrophe!';
    $exclamation =~ s/cat\K\w+!/./;
    #只会替换\K后面的那部分。
    
    like( $exclamation, qr/\bcat\./, "That wasn't so bad!" );
    

    正则修饰符

    修饰符会改变正则表达式的行为,通常是在模式末尾使用。
    禁用大小写敏感:

    my $pet = 'CaMeLiA';
    
    like( $pet, qr/Camelia/, 'Nice butterfly!' );
    like( $pet, qr/Camelia/i, 'shift key br0ken' );
    

    也可以集成在模式中:

    my $some='cat';
    my $find_a_cat = qr/(?<feline>cat)/;
    
    print $+{feline} if $some=~/$find_a_cat/;
    #(?i)语法就表示忽略大小写
    #禁用指定的修饰符可以在前面使用减号,如(?-i)就表示必须大小写一致才会匹配。
    

    其他的修饰符:

    /m 启用多行模式,^和$匹配字符串内的换行符,而非字符串的开头和末尾
    /s 点号可以匹配任何字符,包括换行符
    /r 非破坏方式替换字符
    
    my $status = 'I am hungry for pie.';
    my $newstatus = $status =~ s/pie/cake/r;
    my $statuscopy = $status =~ s/liver and onions/bratwurst/r;
    
    is( $status, 'I am hungry for pie.', 'original string should be unmodified' );
    like( $newstatus, qr/cake/, 'cake wanted' );
    unlike( $statuscopy, qr/bratwurst/, 'wurst not' );
    
    
    /x 可以在模式中增加空白和注释,增加可读性
    my $attr_re = qr{
    \A # start of line
    (?:
    [;\n\s]* # spaces and semicolons
    (?:/\*.*?\*/)? # C comments
    )*
    ATTR
    \s+
    ( U?INTVAL
    | FLOATVAL
    | STRING\s+\*
    )
    }x;
    
    
    /g 全局替换
    # appease the Mitchell estate
    my $contents = slurp( $file );
    $contents =~ s/Scarlett O'Hara/Mauve Midway/g;
    
    
    \G 在上一个匹配处进行匹配
    while ($contents =~ /\G(\w{3})(\w{3})(\w{4})/g)
    {
    push @numbers, "($1) $2-$3";
    }
    
    
    
    /e 修饰符让你具有更灵活的替换字符串能力,由代码运行的返回值作为替代的字符串
    $sequel =~ s{Scarlett( O'Hara)?}
    {
    'Mauve' . defined $1
    ? ' Midway'
    : ''
    }ge;
    #匹配时,执行代码并以返回值替换
    

    相关文章

      网友评论

        本文标题:第六章 正则表达式

        本文链接:https://www.haomeiwen.com/subject/jmugkttx.html