目錄

名稱

perlrequick - Perl 正規表示式快速入門

說明

此頁面涵蓋在 Perl 中理解、建立和使用正規表示式(「regexes」)的基本知識。

指南

此頁面假設您已經知道某些事情,例如「樣式」是什麼,以及使用它們的基本語法。如果您不知道,請參閱 perlretut

簡單的字詞配對

最簡單的正規表示式只是一個字詞,或者更廣泛地說,是一個字元串。由一個字詞組成的正規表示式會配對任何包含該字詞的字串

"Hello World" =~ /World/;  # matches

在此陳述中,World 是正規表示式,而包住 /World/// 告訴 Perl 在字串中搜尋配對。運算子 =~ 將字串與正規表示式配對關聯起來,如果正規表示式配對,則產生真值,如果正規表示式未配對,則產生假值。在我們的案例中,World 配對 "Hello World" 中的第二個字詞,因此這個表達式為真。這個概念有許多變化。

此類表達式在條件式中很有用

print "It matches\n" if "Hello World" =~ /World/;

使用 !~ 運算子可以反轉比對的意義

print "It doesn't match\n" if "Hello World" !~ /World/;

正規表示式中的字串文字可以用變數取代

$greeting = "World";
print "It matches\n" if "Hello World" =~ /$greeting/;

如果要與 $_ 比對,可以省略 $_ =~ 部分

$_ = "Hello World";
print "It matches\n" if /World/;

最後,比對的預設分隔符號 // 可以改成任意分隔符號,只要在前面加上 'm' 即可

"Hello World" =~ m!World!;   # matches, delimited by '!'
"Hello World" =~ m{World};   # matches, note the matching '{}'
"/usr/bin/perl" =~ m"/perl"; # matches after '/usr/bin',
                             # '/' becomes an ordinary char

正規表示式必須與字串的一部分完全相符,陳述式才會為真

"Hello World" =~ /world/;  # doesn't match, case sensitive
"Hello World" =~ /o W/;    # matches, ' ' is an ordinary char
"Hello World" =~ /World /; # doesn't match, no ' ' at end

Perl 會永遠在字串中最早可能的地方進行比對

"Hello World" =~ /o/;       # matches 'o' in 'Hello'
"That hat is red" =~ /hat/; # matches 'hat' in 'That'

並非所有字元都能在比對中「照樣」使用。有些字元,稱為後設字元,被視為特殊字元,並保留在正規表示式符號中使用。後設字元包括

{}[]()^$.|*+?\

後設字元可以在前面加上反斜線後照樣比對

"2+2=4" =~ /2+2/;    # doesn't match, + is a metacharacter
"2+2=4" =~ /2\+2/;   # matches, \+ is treated like an ordinary +
'C:\WIN32' =~ /C:\\WIN/;                       # matches
"/usr/bin/perl" =~ /\/usr\/bin\/perl/;  # matches

在最後一個正規表示式中,正斜線 '/' 也加上反斜線,因為它用於分隔正規表示式。

大部分後設字元並非總是特殊字元,而且其他字元(例如分隔模式的字元)會在各種情況下變成特殊字元。這可能會令人困惑,並導致意外的結果。use re 'strict' 可以通知您潛在的陷阱。

不可列印的 ASCII 字元用跳脫序列表示。常見範例包括表示 tab 的 \t、表示換行的 \n 以及表示回車的 \r。任意位元組用八進位跳脫序列表示,例如 \033,或十六進位跳脫序列表示,例如 \x1B

"1000\t2000" =~ m(0\t2)  # matches
"cat" =~ /\143\x61\x74/  # matches in ASCII, but
                         # a weird way to spell cat

正規表示式大部分視為雙引號字串,因此變數替換有效

$foo = 'house';
'cathouse' =~ /cat$foo/;   # matches
'housecat' =~ /${foo}cat/; # matches

對於以上所有正規表示式,如果正規表示式在字串中的任何地方比對成功,則視為比對成功。若要指定何處應該比對,我們會使用錨定後設字元 ^$。錨定字元 ^ 表示在字串開頭比對,而錨定字元 $ 表示在字串結尾或字串結尾前的換行比對。一些範例

"housekeeper" =~ /keeper/;         # matches
"housekeeper" =~ /^keeper/;        # doesn't match
"housekeeper" =~ /keeper$/;        # matches
"housekeeper\n" =~ /keeper$/;      # matches
"housekeeper" =~ /^housekeeper$/;  # matches

使用字元類別

字元類別允許一組可能的字元,而不仅仅是單一字元,在正規表示式的特定點進行比對。有許多不同類型的字元類別,但通常人們使用這個術語時,指的是本節中描述的類型,技術上稱為「方括號字元類別」,因為它們用方括號 [...] 表示,其中包含可能比對的字元組。但我們會省略「方括號」以符合一般用法。以下是(方括號)字元類別的一些範例

/cat/;            # matches 'cat'
/[bcr]at/;        # matches 'bat', 'cat', or 'rat'
"abc" =~ /[cab]/; # matches 'a'

在最後一個陳述式中,即使 'c' 是類別中的第一個字元,正規表示式最早可以比對的點是 'a'

/[yY][eE][sS]/; # match 'yes' in a case-insensitive way
                # 'yes', 'Yes', 'YES', etc.
/yes/i;         # also match 'yes' in a case-insensitive way

最後一個範例顯示一個使用 'i' 修飾詞 的比對,這會讓比對不分大小寫。

字元類別也有普通字元和特殊字元,但字元類別內的普通字元和特殊字元集合與字元類別外的不同。字元類別的特殊字元為 -]\^$,並使用跳脫字元進行比對

/[\]c]def/; # matches ']def' or 'cdef'
$x = 'bcr';
/[$x]at/;   # matches 'bat, 'cat', or 'rat'
/[\$x]at/;  # matches '$at' or 'xat'
/[\\$x]at/; # matches '\at', 'bat, 'cat', or 'rat'

特殊字元 '-' 在字元類別中作為範圍運算子,因此笨重的 [0123456789][abc...xyz] 變成輕巧的 [0-9][a-z]

/item[0-9]/;  # matches 'item0' or ... or 'item9'
/[0-9a-fA-F]/;  # matches a hexadecimal digit

如果 '-' 是字元類別中的第一個或最後一個字元,則會當作普通字元處理。

字元類別第一個位置的特殊字元 ^ 表示否定字元類別,它會比對括號中的任何字元。[...][^...] 都必須比對到一個字元,否則比對會失敗。然後

/[^a]at/;  # doesn't match 'aat' or 'at', but matches
           # all other 'bat', 'cat, '0at', '%at', etc.
/[^0-9]/;  # matches a non-numeric character
/[a^]at/;  # matches 'aat' or '^at'; here '^' is ordinary

Perl 有幾個常見字元類別的縮寫。(這些定義是 Perl 在 ASCII 安全模式中使用 /a 修飾詞時使用的。否則它們還可以比對更多非 ASCII Unicode 字元。請參閱 "perlrecharclass 中的「跳脫字元序列」 以取得詳細資訊。)

縮寫 \d\s\w\D\S\W 可以用於字元類別內外。以下是一些使用範例

/\d\d:\d\d:\d\d/; # matches a hh:mm:ss time format
/[\d\s]/;         # matches any digit or whitespace character
/\w\W\w/;         # matches a word char, followed by a
                  # non-word char, followed by a word char
/..rt/;           # matches any two chars, followed by 'rt'
/end\./;          # matches 'end.'
/end[.]/;         # same thing, matches 'end.'

字詞錨定 \b 比對字元和非字元之間的界線 \w\W\W\w

$x = "Housecat catenates house and cat";
$x =~ /\bcat/;  # matches cat in 'catenates'
$x =~ /cat\b/;  # matches cat in 'housecat'
$x =~ /\bcat\b/;  # matches 'cat' at end of string

在最後一個範例中,字串的結尾被視為字詞界線。

對於自然語言處理(例如,使字詞包含撇號),請改用 \b{wb}

"don't" =~ / .+? \b{wb} /x;  # matches the whole string

比對這個或那個

我們可以使用交替元字元 '|' 來比對不同的字串。為了比對 dogcat,我們形成正規表示式 dog|cat。和之前一樣,Perl 會嘗試在字串中最早可能的位置比對正規表示式。在每個字元位置,Perl 會先嘗試比對第一個選項 dog。如果 dog 沒有比對到,Perl 接著會嘗試下一個選項 cat。如果 cat 沒有比對到,則比對失敗,Perl 會移到字串中的下一個位置。一些範例

"cats and dogs" =~ /cat|dog|bird/;  # matches "cat"
"cats and dogs" =~ /dog|cat|bird/;  # matches "cat"

即使 dog 是第二個正規表示式中的第一個選項,cat 仍能在字串中較早比對到。

"cats"          =~ /c|ca|cat|cats/; # matches "c"
"cats"          =~ /cats|cat|ca|c/; # matches "cats"

在給定的字元位置,允許正規表示式比對成功的第一個選項將會是比對到的那個。這裡,所有選項都在第一個字串位置比對到,所以第一個比對到。

群組事物和階層式比對

群組元字元 () 允許將正規表示式的一部分視為單一單位。正規表示式的一部分透過將它們括在括號中來群組。正規表示式 house(cat|keeper) 表示比對 house 後面接著 catkeeper。更多範例如下

/(a|b)b/;    # matches 'ab' or 'bb'
/(^a|b)c/;   # matches 'ac' at start of string or 'bc' anywhere

/house(cat|)/;  # matches either 'housecat' or 'house'
/house(cat(s|)|)/;  # matches either 'housecats' or 'housecat' or
                    # 'house'.  Note groups can be nested.

"20" =~ /(19|20|)\d\d/;  # matches the null alternative '()\d\d',
                         # because '20\d\d' can't match

擷取比對結果

群組元字元 () 也允許擷取比對到的字串部分。對於每個群組,比對到的部分會放入特殊變數 $1$2 等。它們可以用作一般變數

# extract hours, minutes, seconds
$time =~ /(\d\d):(\d\d):(\d\d)/;  # match hh:mm:ss format
$hours = $1;
$minutes = $2;
$seconds = $3;

在清單內容中,包含群組的比對 /regex/ 會傳回比對到的值清單 ($1,$2,...)。所以我們可以將它改寫成

($hours, $minutes, $second) = ($time =~ /(\d\d):(\d\d):(\d\d)/);

如果正規表示式中的群組是巢狀的,$1 會取得最左邊開啟括號的群組,$2 會取得下一個開啟括號,依此類推。例如,這裡是一個複雜的正規表示式,以及它下方標示的比對變數

/(ab(cd|ef)((gi)|j))/;
 1  2      34

與匹配變數 $1$2 等相關聯的是 反向參考 \g1\g2 等。反向參考是可以在正規表示式內部使用的匹配變數

/(\w\w\w)\s\g1/; # find sequences like 'the the' in string

$1$2 等只能在正規表示式外部使用,而 \g1\g2 等只能在正規表示式內部使用。

匹配重複

量詞元字元 ?*+{} 讓我們可以確定我們視為匹配的正規表示式一部分的重複次數。量詞放在我們想要指定的字元、字元類別或分組之後。它們具有下列意義

以下是一些範例

/[a-z]+\s+\d*/;  # match a lowercase word, at least some space, and
                 # any number of digits
/(\w+)\s+\g1/;    # match doubled words of arbitrary length
$year =~ /^\d{2,4}$/;  # make sure year is at least 2 but not more
                       # than 4 digits
$year =~ /^\d{ 4 }$|^\d{2}$/; # better match; throw out 3 digit dates

這些量詞會嘗試匹配字串的盡可能多部分,同時仍然允許正規表示式匹配。因此我們有

$x = 'the cat in the hat';
$x =~ /^(.*)(at)(.*)$/; # matches,
                        # $1 = 'the cat in the h'
                        # $2 = 'at'
                        # $3 = ''   (0 matches)

第一個量詞 .* 擷取字串的盡可能多部分,同時仍然讓正規表示式匹配。第二個量詞 .* 沒有字串可以擷取,因此它匹配 0 次。

更多匹配

有一些關於匹配運算子的其他事項您可能想要知道。全域修改器 /g 允許匹配運算子在字串中盡可能多次匹配。在純量內容中,對字串的連續匹配會讓 /g 從匹配跳到匹配,同時追蹤字串中的位置。您可以使用 pos() 函數取得或設定位置。例如,

$x = "cat dog house"; # 3 words
while ($x =~ /(\w+)/g) {
    print "Word is $1, ends at position ", pos $x, "\n";
}

列印

Word is cat, ends at position 3
Word is dog, ends at position 7
Word is house, ends at position 13

失敗的匹配或變更目標字串會重設位置。如果您不希望在失敗匹配後重設位置,請新增 /c,例如 /regex/gc

在清單內容中,/g 會傳回已匹配分組的清單,或者如果沒有分組,則傳回與整個正規表示式匹配的清單。因此

@words = ($x =~ /(\w+)/g);  # matches,
                            # $word[0] = 'cat'
                            # $word[1] = 'dog'
                            # $word[2] = 'house'

搜尋和取代

搜尋和取代使用 s/regex/replacement/modifiers 執行。replacement 是 Perl 雙引號字串,它會在字串中取代與 regex 匹配的任何內容。運算子 =~ 也用於在此將字串與 s/// 關聯起來。如果與 $_ 匹配,則可以省略 $_ =~。如果有匹配,s/// 會傳回已進行的取代次數;否則,它會傳回 false。以下是一些範例

$x = "Time to feed the cat!";
$x =~ s/cat/hacker/;   # $x contains "Time to feed the hacker!"
$y = "'quoted words'";
$y =~ s/^'(.*)'$/$1/;  # strip single quotes,
                       # $y contains "quoted words"

使用 s/// 算子,匹配的變數 $1$2 等可立即用於替換表達式中。使用全域修飾詞 s///g 會搜尋並替換字串中所有出現的正規表示式

$x = "I batted 4 for 4";
$x =~ s/4/four/;   # $x contains "I batted four for 4"
$x = "I batted 4 for 4";
$x =~ s/4/four/g;  # $x contains "I batted four for four"

非破壞性修飾詞 s///r 會導致替換結果傳回,而不是修改 $_(或替換繫結至 =~ 的任何變數)

$x = "I like dogs.";
$y = $x =~ s/dogs/cats/r;
print "$x $y\n"; # prints "I like dogs. I like cats."

$x = "Cats are great.";
print $x =~ s/Cats/Dogs/r =~ s/Dogs/Frogs/r =~
    s/Frogs/Hedgehogs/r, "\n";
# prints "Hedgehogs are great."

@foo = map { s/[a-z]/X/r } qw(a b c 1 2 3);
# @foo is now qw(X X X 1 2 3)

評估修飾詞 s///e 會將 eval{...} 包在替換字串周圍,並將評估結果替換為匹配的子字串。一些範例

# reverse all the words in a string
$x = "the cat in the hat";
$x =~ s/(\w+)/reverse $1/ge;   # $x contains "eht tac ni eht tah"

# convert percentage to decimal
$x = "A 39% hit rate";
$x =~ s!(\d+)%!$1/100!e;       # $x contains "A 0.39 hit rate"

最後一個範例顯示 s/// 可以使用其他分隔符,例如 s!!!s{}{},甚至是 s{}//。如果使用單引號 s''',則正規表示式和替換會被視為單引號字串。

分割算子

split /regex/, string 會將 string 分割成子字串清單,並傳回該清單。正規表示式會決定 string 根據什麼字元序列分割。例如,要將字串分割成字詞,請使用

$x = "Calvin and Hobbes";
@word = split /\s+/, $x;  # $word[0] = 'Calvin'
                          # $word[1] = 'and'
                          # $word[2] = 'Hobbes'

要萃取逗號分隔的數字清單,請使用

$x = "1.618,2.718,   3.142";
@const = split /,\s*/, $x;  # $const[0] = '1.618'
                            # $const[1] = '2.718'
                            # $const[2] = '3.142'

如果使用空正規表示式 //,則會將字串分割成個別字元。如果正規表示式有群組,則產生的清單也會包含群組中匹配的子字串

$x = "/usr/bin";
@parts = split m!(/)!, $x;  # $parts[0] = ''
                            # $parts[1] = '/'
                            # $parts[2] = 'usr'
                            # $parts[3] = '/'
                            # $parts[4] = 'bin'

由於 $x 的第一個字元與正規表示式相符,因此 split 會在清單前面加上一個空的初始元素。

use re 'strict'

v5.22 的新功能,在編譯正規表示式模式時,會套用比其他情況更嚴格的規則。它可以找到一些合法的東西,但可能不是您想要的。

請參閱 'strict' in re

錯誤

無。

另請參閱

這只是一個快速入門指南。有關正規表示式的更深入教學,請參閱 perlretut,有關參考頁面,請參閱 perlre

作者和著作權

版權所有 (c) 2000 Mark Kvale 保留所有權利。

本文件可以在與 Perl 相同的條款下散布。

致謝

作者要感謝 Mark-Jason Dominus、Tom Christiansen、Ilya Zakharevich、Brad Hughes 和 Mike Giroux 提供所有有用的意見。