1. 程式人生 > >Perl正則表達式引用

Perl正則表達式引用

perl正則 lag 轉換成 mit word 不同版本 passenger 賦值 擴展

正則表達式兩篇:

  • 基礎正則
  • Perl正則

本文是對Perl正則的一點擴展,主要內容是使用qr//創建正則對象,以及一些其它的技巧。

qr//創建正則對象

因為可以在正則模式中使用變量替換,所以我們可以將正則中的一部分表達式事先保存在變量中。例如:

$str="hello worlds gaoxiaofang";
$pattern="w.*d";
$str =~ /$pattern/;
print "$&\n";

但是,這樣缺陷很大,在保存正則表達式的變量中存放的特殊字符要防止有特殊意義。例如,當使用m//的方式做匹配分隔符時,不能在變量中保存/

,除非轉義。

perl提供了qr/pattern/的功能,它把pattern部分構建成一個正則表達式對象,然後就可以:

  • 在正則表達式中直接引用這個對象
  • 可以將這個對象保存到變量中,通過引用變量的方式來引用這個已保存好的正則對象
  • 將引用變量插入到其它模式中構建更復雜的正則表達式

其中:

  • qr//的定界符斜線可以替換為其它符號,例如對稱的括號類qr() qr{} qr<> qr[],一致的符號類qr%% qr## qr!! qr$$ qr"" qr‘‘等。
  • 但是使用單引號作為定界符時比較特殊(即qr‘pattern‘),它會將pattern部分使用單引號的方式去解析,例如變量$var
    無法替換,而是表示4個字符。但是正則表達式的元字符仍然起作用,例如$仍然表示行尾。
$str="hello worlds gaoxiaofang";

# 直接作為正則表達式
$str =~ qr/w.*d/;
print "$&\n";

# 保存為變量,再作為正則表達式
$pattern=qr/w.*d/;
$str =~ $pattern;    # (1)
$str =~ /$pattern/;  # (2)
print "$&\n";

# 保存為變量,作為正則表達式的一部分
$pattern=qr/w.*d/;
$str =~ /hel.* $pattern/;
print "$&\n";

還允許為這個正則對象設置修飾符,比如忽略大小寫的匹配修飾符為i,這樣在真正匹配的時候,就只有這一部分正則對象會忽略大小寫,其余部分仍然區分大小寫。

$str="HELLO wORLDs gaoxiaofang";

$pattern=qr/w.*d/i;         # 忽略大小寫

$str =~ /HEL.* $pattern/;   # 匹配成功,$pattern部分忽略大小寫
$str =~ /hel.* $pattern/;   # 匹配失敗
$str =~ /hel.* $pattern/i;  # 匹配成功,所有都忽略大小寫

qr如何構建正則對象

輸出qr構建的正則引用,看看是怎樣的結構:

$patt1=qr/w.*d/;
print "$patt1\n";

$patt2=qr/w.*d/i;    # 加上修飾符i
print "$patt2\n";

$patt3=qr/w.*d/img;  # 加上修飾符img
print "$patt3\n";

上面的print將輸出如下結果:

(?^:w.*d)
(?^i:w.*d)
(?^mi:w.*d)

qr的作用實際上就是在我們給定的正則pattern基礎上加上(?^:)並帶上一些修飾符,得到的結果總是(?^FLAGS:pattern)

但是上面patt3的修飾符g不見了。先可以看看(?^:)的作用:非捕獲分組,並重置修飾符。重置為哪些修飾符?對於(?^FLAGS:)來說,只有這些修飾符"alupimsx"是可用的,即(?^alupimsx:)

  • 如果給定的修飾符不在這些修飾符內,則不被識別,有時候會報錯
  • 如果給定的修飾符屬於這幾個修飾符,那麽沒有給定的修飾符部分將采用默認值(不同版本可能默認是否開啟的值不同)

所以上面的g會被丟棄,甚至在進一步操作這個正則引用時,會報錯。

既然qr給pattern部分加上了(?^:),那麽當它們插入到其它正則中的時候,就能保證這一段是獨立的,不受全局修飾符影響的模式。

$patt1=qr/w.*d/im;
$patt2=qr/hel.*d $patt1/i;
print "$patt2\n";     # 輸出:(?^i:hel.*d (?^mi:w.*d))

正則引用作為標量的用法

既然qr//創建的正則對象引用是一個標量,那麽標量可以出現的地方,正則引用就可以出現。例如,放進hash結構,數組結構。

例如,放進數組中形成一個正則表達式列表,然後給定一個待匹配目標,依次用列表中的這些模式去匹配。

use v5.10.1;
my @patterns = (
    qr/(?:Willie )?Gilligan/,
    qr/Mary Ann/,
    qr/Ginger/,
    qr/(?:The )?Professor/,
    qr/Skipper/,
    qr/Mrs?. Howell/,
);

my $name = 'Ginger';
foreach my $pattern ( @patterns ) {
    if( $name =~ /$pattern/ ) {
        say "Match!";
        print "$pattern";
        last;
    }
}

還可以將這些正則引用放進hash中,為每個pattern都使用key來標識一下,例如pattern1是用來匹配什麽的:

use v5.10.1;
my %patterns = (
    Gilligan => qr/(?:Willie )?Gilligan/,
    'Mary Ann' => qr/Mary Ann/,
    Ginger => qr/Ginger/,
    Professor => qr/(?:The )?Professor/,
    Skipper => qr/Skipper/,
    'A Howell' => qr/Mrs?. Howell/,
);
my $name = 'Ginger';
my( $match ) = grep { $name =~ $patterns{$_} } keys %patterns;
say "Matched $match" if $match;

上面將grep語句的結果賦值給了一個標量,所以如果有多個Pattern能匹配$name,多次執行,$match的值將可能會不一樣。

構建復雜的正則表達式

有了qr,就可以將正則表達式細化成一小片一小片,然後組合起來。例如:

my $howells = qr/Thurston|Mrs/;
my $tagalongs = qr/Ginger|Mary Ann/;
my $passengers = qr/$howells|$tagalongs/;
my $crew = qr/Gilligan|Skipper/;
my $everyone = qr/$crew|$passengers/;

就像RFC 1738中對URL各個部分的解剖,如果轉換成Perl正則,大概是這樣的(了解即可):

# 可復用的基本符號類
my $alpha = qr/[a?z]/;
my $digit = qr/\d/;
my $alphadigit = qr/(?i:$alpha|$digit)/;
my $safe = qr/[\$_.+?]/;
my $extra = qr/[!*'\(\),]/;
my $national = qr/[{}|\\^~\[\]`]/;
my $reserved = qr|[;/?:@&=]|;
my $hex = qr/(?i:$digit|[A?F])/;
my $escape = qr/%$hex$hex/;
my $unreserved = qr/$alpha|$digit|$safe|$extra/;
my $uchar = qr/$unreserved|$escape/;
my $xchar = qr/$unreserved|$reserved|$escape/;
my $ucharplus = qr/(?:$uchar|[;?&=])*/;
my $digits = qr/(?:$digit){1,}/;

# 可復用的URL組成元素
my $hsegment = $ucharplus;
my $hpath = qr|$hsegment(?:/$hsegment)*|;
my $search = $ucharplus;
my $scheme = qr|(?i:https?://)|;
my $port = qr/$digits/;
my $password = $ucharplus;
my $user = $ucharplus;

my $toplevel = qr/$alpha|$alpha(?:$alphadigit|?)*$alphadigit/;
my $domainlabel = qr/$alphadigit|$alphadigit(?:$alphadigit|?)*$alphadigit/x;
my $hostname = qr/(?:$domainlabel\.)*$toplevel/;
my $hostnumber = qr/$digits\.$digits\.$digits\.$digits/;
my $host = qr/$hostname|$hostnumber/;
my $hostport = qr/$host(?::$port)?/;
my $login = qr/(?:$user(?::$password)\@)?/;

my $urlpath = qr/(?:(?:$xchar)*)/;

然後我們就可以用上面看上去無比復雜的正則表達式去匹配一個路徑是否是合格的http url:

use v5.10.1;
my $httpurl = qr|$scheme$hostport(?:/$hpath(?:\?$search)?)?|;
while( <> ) {
    say if /$httpurl/;
}

正則表達式模塊

上面構建的正則太復雜了,很多常用的正則表達式別人已經造好了輪子,我們直接拿來用就行了。例如,Regexp::Common模塊,提供了很多種已經構建好的正則表達式。

首先安裝這個模塊:

sudo cpan -i Regexp::Common

以下是CPAN上提供的Regexp::Common已造好的輪子,可參考:https://metacpan.org/release/Regexp-Common

Regexp::Common - Provide commonly requested regular expressions
Regexp::Common::CC - provide patterns for credit card numbers.
Regexp::Common::SEN - provide regexes for Social-Economical Numbers.
Regexp::Common::URI - provide patterns for URIs.
Regexp::Common::URI::RFC1035 - Definitions from RFC1035;
Regexp::Common::URI::RFC1738 - Definitions from RFC1738;
Regexp::Common::URI::RFC1808 - Definitions from RFC1808;
Regexp::Common::URI::RFC2384 - Definitions from RFC2384;
Regexp::Common::URI::RFC2396 - Definitions from RFC2396;
Regexp::Common::URI::RFC2806 - Definitions from RFC2806;
Regexp::Common::URI::fax - Returns a pattern for fax URIs.
Regexp::Common::URI::file - Returns a pattern for file URIs.
Regexp::Common::URI::ftp - Returns a pattern for FTP URIs.
Regexp::Common::URI::gopher - Returns a pattern for gopher URIs.
Regexp::Common::URI::http - Returns a pattern for HTTP URIs.
Regexp::Common::URI::news - Returns a pattern for file URIs.
Regexp::Common::URI::pop - Returns a pattern for POP URIs.
Regexp::Common::URI::prospero - Returns a pattern for prospero URIs.
Regexp::Common::URI::tel - Returns a pattern for telephone URIs.
Regexp::Common::URI::telnet - Returns a pattern for telnet URIs.
Regexp::Common::URI::tv - Returns a pattern for tv URIs.
Regexp::Common::URI::wais - Returns a pattern for WAIS URIs.
Regexp::Common::_support - Support functions for Regexp::Common.
Regexp::Common::balanced - provide regexes for strings with balanced parenthesized delimiters or arbitrary delimiters.
Regexp::Common::comment - provide regexes for comments.
Regexp::Common::delimited - provides a regex for delimited strings
Regexp::Common::lingua - provide regexes for language related stuff.
Regexp::Common::list - provide regexes for lists
Regexp::Common::net - provide regexes for IPv4, IPv6, and MAC addresses.
Regexp::Common::number - provide regexes for numbers
Regexp::Common::profanity - provide regexes for profanity
Regexp::Common::whitespace - provides a regex for leading or trailing whitescape
Regexp::Common::zip - provide regexes for postal codes.

這些正則表達式是通過hash進行嵌套的,hash的名稱為%RE。例如模塊Regexp::Common::URI::http,它提供的是HTTP URI的正則表達式,它嵌套了兩層,第一層的key為URI,這個key對應的值是第二層hash,第二層hash的key為HTTP,於是可以通過$RE{URI}{HTTP}的方式獲取這個正則。

例如,匹配一個http url是否合理:

use Regexp::Common qw(URI);
while( <> ) {
    print if /$RE{URI}{HTTP}/;
}

在學習shell腳本的時候,經常有人寫匹配IPV4的正則表達式,現在我們可用直接從Regexp::Common::net中獲取:

use Regexp::Common qw(net);
$ipv4=$RE{net}{IPv4};
print $ipv4;

以下是結果:

(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))

只是需要註意的是,在真正匹配的時候應該將得到的引用錨定起來,否則對318.99.183.11進行匹配的時候也會返回true,因為18.99.183.11是符合匹配結果的。所以,對前後都加上錨定,例如:

$ipv4 =~ /^$RE{net}{IPv4}$/;

將上面的ipv4正則改造一下(去掉非捕獲分組的功能),讓它適用於shell工具中普遍支持的擴展正則:

(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})){3}

默認情況下,Regexp::Common的各個模塊是沒有開啟捕獲功能的。如果要使用$1$N這種引用,需要使用{-keep}選項,至於每個分組捕獲的是什麽內容,需要參考幫助文檔的說明。

例如:

use Regexp::Common qw(number);
while( <> ) {
    say $1 if /$RE{num}{int}{ ?base => 16 }{?keep}/;
}

Perl正則表達式引用