標籤:edit grep exp perlRegex 基礎 regular 常用 意義 efi
Regex兩篇:
本文是對Perl正則的一點擴充,主要內容是使用qr//
建立正則對象,以及一些其它的技巧。
qr//建立正則對象
因為可以在正則模式中使用變數替換,所以我們可以將正則中的一部分運算式事先儲存在變數中。例如:
$str="hello worlds gaoxiaofang";$pattern="w.*d";$str =~ /$pattern/;print "$&\n";
但是,這樣缺陷很大,在儲存Regex的變數中存放的特殊字元要防止有特殊意義。例如,當使用m//
的方式做匹配分隔字元時,不能在變數中儲存/
,除非轉義。
perl提供了qr/pattern/
的功能,它把pattern部分構建成一個Regex對象,然後就可以:
- 在Regex中直接引用這個對象
- 可以將這個對象儲存到變數中,通過引用變數的方式來引用這個已儲存好的正則對象
- 將引用變數插入到其它模式中構建更複雜的Regex
其中:
qr//
的定界符斜線可以替換為其它符號,例如對稱的括弧類qr() qr{} qr<> qr[]
,一致的符號類qr%% qr## qr!! qr$$ qr"" qr‘‘
等。
- 但是使用單引號作為定界符時比較特殊(即
qr‘pattern‘
),它會將pattern部分使用單引號的方式去解析,例如變數$var
無法替換,而是表示4個字元。但是Regex的元字元仍然起作用,例如$
仍然表示行尾。
$str="hello worlds gaoxiaofang";# 直接作為Regex$str =~ qr/w.*d/;print "$&\n";# 儲存為變數,再作為Regex$pattern=qr/w.*d/;$str =~ $pattern; # (1)$str =~ /$pattern/; # (2)print "$&\n";# 儲存為變數,作為Regex的一部分$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; # 加上修飾符iprint "$patt2\n";$patt3=qr/w.*d/img; # 加上修飾符imgprint "$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結構,數組結構。
例如,放進數組中形成一個Regex列表,然後給定一個待匹配目標,依次用列表中的這些模式去匹配。
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
的值將可能會不一樣。
構建複雜的Regex
有了qr,就可以將Regex細化成一小片一小片,然後組合起來。例如:
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)*)/;
然後我們就可以用上面看上去無比複雜的Regex去匹配一個路徑是否是合格的http url:
use v5.10.1;my $httpurl = qr|$scheme$hostport(?:/$hpath(?:\?$search)?)?|;while( <> ) { say if /$httpurl/;}
Regex模組
上面構建的正則太複雜了,很多常用的Regex別人已經造好了輪子,我們直接拿來用就行了。例如,Regexp::Common
模組,提供了很多種已經構建好的Regex。
首先安裝這個模組:
sudo cpan -i Regexp::Common
以下是CPAN上提供的Regexp::Common
已造好的輪子,可參考:https://metacpan.org/release/Regexp-Common
Regexp::Common - Provide commonly requested regular expressionsRegexp::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 stringsRegexp::Common::lingua - provide regexes for language related stuff.Regexp::Common::list - provide regexes for listsRegexp::Common::net - provide regexes for IPv4, IPv6, and MAC addresses.Regexp::Common::number - provide regexes for numbersRegexp::Common::profanity - provide regexes for profanityRegexp::Common::whitespace - provides a regex for leading or trailing whitescapeRegexp::Common::zip - provide regexes for postal codes.
這些Regex是通過hash進行嵌套的,hash的名稱為%RE
。例如模組Regexp::Common::URI::http
,它提供的是HTTP URI的Regex,它嵌套了兩層,第一層的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的Regex,現在我們可用直接從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}/;}
PerlRegex引用