正則表達式是使用單個字符串來描述、匹配一系列符合某個句法規則的字符串。在很多文本編輯器里,正則表達式通常被用來檢索、替換那些符合某個模式的文本。當前常見的正則表達式主要有兩種派系,一種是perl衍生出來的PCRE(Perl Compatible Regular Expression),另一種是POSIX規范兼容的正則表達式BRE和ERE。本文主要對其進行總結和對比。
一、Perl正則表達式
許多程序設計語言都支持利用正則表達式進行字符串操作。在Perl中就內建了一個功能強大的正則表達式引擎,受到眾多程序員的認可,甚至一些其他語言(如JAVA)的正則表達式引擎也參考了Perl。
1)簡介
因篇幅限制,本文僅僅討論正則表達式,不過多介紹Perl的語言特性。作為背景,這里只簡要介紹Perl語言里和正則表達式相關的一些語法。
- 在 Perl 程序中,正則表達式有三種存在形式,他們分別是:
匹配:m/<regexp>/ d(還可以簡寫為 /<regexp>/d )
替換:s/<pattern>/<replacement>/ d
轉化:tr/<pattern>/<replacemnt>/d
其中模式匹配分隔符『/』可以用任意其他符號代替,如s|<pattern>|<replacement>|
『d』為可選修飾符號,有i(不區分大小寫)、x(添加空格)、g(全局匹配)等
- Perl默認對$_進行匹配,如果要對其他容器進行匹配則需要用到綁定操作符『=~』或『~=』(右邊匹配左邊)
- 自動匹配變量:『$&』, 『$`』, 『$' 』 ,三個值一起得到原始內容;
匹配上的那部分字串自動存儲在『$&』中,前一部分存儲在『$`』中,而后一部分存儲在『$' 』中。
2)匹配字符
字符 | 描述 |
---|---|
\ | 將下一個字符標記為一個特殊字符、或一個原義字符、或一個向后引用、或一個八進制轉義符。例如,“n "匹配字符"n "。"\n "匹配一個換行符。串行"\\ "匹配"\ "而"\( "則匹配"( "。 |
^ | 匹配輸入字符串的開始位置。如果設置了RegExp對象的Multiline屬性,^也匹配“\n "或"\r "之后的位置。 |
$ | 匹配輸入字符串的結束位置。如果設置了RegExp對象的Multiline屬性,$也匹配“\n "或"\r "之前的位置。 |
* | 匹配前面的子表達式零次或多次。例如,zo*能匹配“z "以及"zoo "。*等價於{0,}。 |
+ | 匹配前面的子表達式一次或多次。例如,“zo+ "能匹配"zo "以及"zoo ",但不能匹配"z "。+等價於{1,}。 |
? | 匹配前面的子表達式零次或一次。例如,“do(es)? "可以匹配"does "或"does "中的"do "。?等價於{0,1}。 |
{n} | n是一個非負整數。匹配確定的n次。例如,“o{2} "不能匹配"Bob "中的"o ",但是能匹配"food "中的兩個o。 |
{n,} | n是一個非負整數。至少匹配n次。例如,“o{2,} "不能匹配"Bob "中的"o ",但能匹配"foooood "中的所有o。"o{1,} "等價於"o+ "。"o{0,} "則等價於"o* "。 |
{n,m} | m和n均為非負整數,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3} "將匹配"fooooood "中的前三個o。"o{0,1} "等價於"o? "。請注意在逗號和兩個數之間不能有空格。 |
? | 當該字符緊跟在任何一個其他限制符(*,+,?,{n},{n,},{n,m})后面時,匹配模式是非貪婪的。非貪婪模式盡可能少的匹配所搜索的字符串,而默認的貪婪模式則盡可能多的匹配所搜索的字符串。例如,對於字符串“oooo ","o+? "將匹配單個"o ",而"o+ "將匹配所有"o "。 |
. | 匹配除“\ n "之外的任何單個字符。要匹配包括"\ n "在內的任何字符,請使用像"(.|\n) "的模式。 |
(pattern) | 匹配pattern並獲取這一匹配。所獲取的匹配可以從產生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中則使用$0…$9屬性。要匹配圓括號字符,請使用“\( "或"\) "。 |
(?:pattern) | 匹配pattern但不獲取匹配結果,也就是說這是一個非獲取匹配,不進行存儲供以后使用。這在使用或字符“(|) "來組合一個模式的各個部分是很有用。例如"industr(?:y|ies) "就是一個比"industry|industries "更簡略的表達式。 |
(?=pattern) | 正向肯定預查,在任何匹配pattern的字符串開始處匹配查找字符串。這是一個非獲取匹配,也就是說,該匹配不需要獲取供以后使用。例如,“Windows(?=95|98|NT|2000) "能匹配"Windows2000 "中的"Windows ",但不能匹配"Windows3.1 "中的"Windows "。預查不消耗字符,也就是說,在一個匹配發生后,在最后一次匹配之后立即開始下一次匹配的搜索,而不是從包含預查的字符之后開始。 |
(?!pattern) | 正向否定預查,在任何不匹配pattern的字符串開始處匹配查找字符串。這是一個非獲取匹配,也就是說,該匹配不需要獲取供以后使用。例如“Windows(?!95|98|NT|2000) "能匹配"Windows3.1 "中的"Windows ",但不能匹配"Windows2000 "中的"Windows "。預查不消耗字符,也就是說,在一個匹配發生后,在最后一次匹配之后立即開始下一次匹配的搜索,而不是從包含預查的字符之后開始 |
(?<=pattern) | 反向肯定預查,與正向肯定預查類擬,只是方向相反。例如,“(?<=95|98|NT|2000)Windows "能匹配"2000Windows "中的"Windows ",但不能匹配"3.1Windows "中的"Windows "。 |
(?<!pattern) | 反向否定預查,與正向否定預查類擬,只是方向相反。例如“(?<!95|98|NT|2000)Windows "能匹配"3.1Windows "中的"Windows ",但不能匹配"2000Windows "中的"Windows "。 |
x|y | 匹配x或y。例如,“z|food "能匹配"z "或"food "。"(z|f)ood "則匹配"zood "或"food "。 |
[xyz] | 字符集合。匹配所包含的任意一個字符。例如,“[abc] "可以匹配"plain "中的"a "。 |
[^xyz] | 負值字符集合(補集)。匹配未包含的任意字符。例如,“[^abc] "可以匹配"plain "中的"p "。 |
[a-z] | 字符范圍。匹配指定范圍內的任意字符。例如,“[a-z] "可以匹配"a "到"z "范圍內的任意小寫字母字符。 |
[^a-z] | 負值字符范圍。匹配任何不在指定范圍內的任意字符。例如,“[^a-z] "可以匹配任何不在"a "到"z "范圍內的任意字符。 |
\b | 匹配一個單詞邊界,也就是指單詞和空格間的位置。例如,“er\b "可以匹配"never "中的"er ",但不能匹配"verb "中的"er "。 |
\B | 匹配非單詞邊界。“er\B "能匹配"verb "中的"er ",但不能匹配"never "中的"er "。 |
\cx | 匹配由x指明的控制字符。例如,\cM匹配一個Control-M或回車符。x的值必須為A-Z或a-z之一。否則,將c視為一個原義的“c "字符。 |
\d | 匹配一個數字字符。等價於[0-9]。 |
\D | 匹配一個非數字字符。等價於[^0-9]。 |
\f | 匹配一個換頁符。等價於\x0c和\cL。 |
\n | 匹配一個換行符。等價於\x0a和\cJ。 |
\r | 匹配一個回車符。等價於\x0d和\cM。 |
\s | 匹配任何空白字符,包括空格、制表符、換頁符等等。等價於[ \f\n\r\t\v]。 |
\S | 匹配任何非空白字符。等價於[^ \f\n\r\t\v]。 |
\t | 匹配一個制表符。等價於\x09和\cI。 |
\v | 匹配一個垂直制表符。等價於\x0b和\cK。 |
\w | 匹配包括下划線的任何單詞字符。等價於“[A-Za-z0-9_] "。 |
\W | 匹配任何非單詞字符。等價於“[^A-Za-z0-9_] "。 |
\xn | 匹配n,其中n為十六進制轉義值。十六進制轉義值必須為確定的兩個數字長。例如,“\x41 "匹配"A "。"\x041 "則等價於"\x04&1 "。正則表達式中可以使用ASCII編碼。. |
\num | 匹配num,其中num是一個正整數。對所獲取的匹配的引用。例如,“(.)\1 "匹配兩個連續的相同字符。 |
\n | 標識一個八進制轉義值或一個向后引用。如果\n之前至少n個獲取的子表達式,則n為向后引用。否則,如果n為八進制數字(0-7),則n為一個八進制轉義值。 |
\nm | 標識一個八進制轉義值或一個向后引用。如果\nm之前至少有nm個獲得子表達式,則nm為向后引用。如果\nm之前至少有n個獲取,則n為一個后跟文字m的向后引用。如果前面的條件都不滿足,若n和m均為八進制數字(0-7),則\nm將匹配八進制轉義值nm。 |
\nml | 如果n為八進制數字(0-3),且m和l均為八進制數字(0-7),則匹配八進制轉義值nml。 |
\un | 匹配n,其中n是一個用四個十六進制數字表示的Unicode字符。例如,\u00A9匹配版權符號(©)。 |
3)POSIX字符類
Perl也支持POSIX字符類的匹配(關於POSIX字符類的詳細說明見第二部分),表示語法:[:class:], 在pattern中則必須寫為"[[:class:]]"。
在Perl中,"[[^:class:]]"表示posix指定類的補集,這種情況下也可略去[::],在類名前加'^'表示為"[^class]"。
二、POSIX規范正則表達式
POSIX的全稱是Portable Operating System Interface for uniX,它由一系列規范構成,定義了UNIX操作系統應當支持的功能,所以“POSIX規范的正則表達式”其實只是“關於正則表達式的POSIX規范”,它定義了BRE(Basic Regular Expression,基本型正則表達式)和ERE(Extended Regular Express,擴展型正則表達式)。在兼容POSIX的UNIX系統上,vi/vim, awk, grep和sed之類的工具都遵循POSIX規范,一些數據庫系統中的正則表達式也符合POSIX規范。
1)BRE
在Linux/Unix常用工具中,grep、vi、sed都屬於BRE這一派,它的語法看起來比較奇怪,元字符『(』、『)』、『{』、『}』必 須轉義之后才具有特殊含義,所以正則表達式『(a)b』只能匹配字符串 (a)b而不是字符串ab;
正則表達式『a{1,2}』只能匹配字符串a{1,2},正則表達式『a\{1,2\}』才能匹配字符串a或者aa。
之所以這么麻煩,是因為這些工具的誕生時間很早,正則表達式的許多功能卻是逐步發展演化出來的,之前這些元字符可能並沒有特殊的含義;為保證向后兼容,就只能使用轉義。而且有些功能甚至根本就不支持,
比如BRE就不支持『+』和『?』量詞,也不支持多選結構『(…|…)』和反向引用『\1』、 『\2』…
不過在今天,純粹的BRE已經很少見了,畢竟大家已經認為正則表達式“理所應當”支持多選結構和反向引用等功能,沒有確實太不方便。所以雖然vi屬於 BRE流派,但提供了這些功能。
GNU也對BRE做了擴展,支持『+』、『?』、『|』,只是使用時必須寫成『\+』、『\?』、『\|』,而且也支持 『\1』、『\2』之類反向引用。這樣,GNU的grep等工具雖然名義上屬於BRE流,但更確切的名稱是GNU BRE。
2)ERE
在Linux/Unix常用工具中,egrep、awk則屬於ERE這一派。雖然BRE名為“基本”而ERE名為“擴展”,但ERE並不要求兼容 BRE的語法,而是自成一體。因此其中的元字符不用轉義(在元字符之前添加反斜線會取消其特殊含義),
所以『(ab|cd)』就可以匹配字符串ab或者 cd,量詞『+』、『?』、『{n,m}』可以直接使用。ERE並沒有明確規定支持反向引用,但是不少工具都支持『\1』、『\2』之類的反向引用。
GNU出品的egrep等工具就屬於ERE流(更准確的名字是GNU ERE),但因為GNU已經對BRE做了不少擴展,所謂的GNU ERE其實只是個說法而已,它有的功能GNU BRE都有了,只是元字符不需要轉義而已。
下面的表格簡要說明了幾種POSIX流派的區別(其實,現在的BRE和ERE在功能上並沒有什么區別,主要的差異是在元字符的轉義上)。
3)POSIX字符組
在某些文檔中,你還會發現類似『[:digit:]』、『[:lower:]』之類的表示法,它們看起來不難理解(digit就是“數 字”,lower就是“小寫”),但又很奇怪,這就是POSIX字符組。不僅在Linux/Unix的常見工具中,甚至一些編程語言中都出現了這些字符組,
為避免困惑,這里有必要簡要介紹它們。
在POSIX規范中,『[a-z]』、『[aeiou]』之類的記法仍然是合法的,其意義與PCRE中的字符組也沒有區別,只是這類記法的准確名稱 是POSIX方括號表達式(bracket expression),它主要用在Unix/Linux系統中。
POSIX方括號表示法與PCRE字符組的最主要差別在於:POSIX字符組中,反斜 線\不是用來轉義的。所以POSIX方括號表示法『[\d]』只能匹配\和d兩個字符,而不是『[0-9]』對應的數字字符。
為了解決字符組中特殊意義字符的轉義問題,POSIX方括號表示法規定,如果要在字符組中表達字符](而不是作為字符組的結束標記),應當讓它緊跟 在字符組的開方括號之后,所以POSIX中,
正則表達式『[]a]』能匹配的字符就是]和a;如果要在POSIX方括號表示法中表達字符-(而不是范圍表 示法),必須將它緊挨在閉方括號]之前,所以『[a-]』能匹配的字符就是a和-。
POSIX規范也定義了POSIX字符組,它近似等價於於PCRE的字符組簡記法,用一個有直觀意義的名字來表示某一組字符,比如digit表示“數字字符”,alpha表示“字母字符”。
不過,POSIX中還有一個值得注意的概念:locale(通常翻譯為“語言環境”)。它是一組與語言和文化相關的設定,包括日期格式、貨幣幣值、 字符編碼等等。POSIX字符組的意義會根據locale的變化而變化,
下面表介紹了常見的POSIX字符組在ASCII語言環境與Unicode語言環境下的意義:
POSIX字符組 |
說明 |
ASCII語言環境 |
Unicode語言環境 |
[:alnum:]* |
字母字符和數字字符 |
[a-zA-Z0-9] |
[\p{L&}\p{Nd}] |
[:alpha:] |
字母 |
[a-zA-Z] |
\p{L&} |
[:ascii:] |
ASCII字符 |
[\x00-\x7F] |
\p{InBasicLatin} |
[:blank:] |
空格字符和制表符 |
[ \t] |
[\p{Zs}\t] |
[:cntrl:] |
控制字符 |
[\x00-\x1F\x7F] |
\p{Cc} |
[:digit:] |
數字字符 |
[0-9] |
\p{Nd} |
[:graph:] |
空白字符之外的字符 |
[\x21-\x7E] |
[^\p{Z}\p{C}] |
[:lower:] |
小寫字母字符 |
[a-z] |
\p{Ll} |
[:print:] |
類似[:graph:],但包括空白字符 |
[\x20-\x7E] |
\P{C} |
[:punct:] |
標點符號 |
[][!"#$%&'()*+,./:;<=>?@\^_`{|}~-] |
[\p{P}\p{S}] |
[:space:] |
空白字符 |
[ \t\r\n\v\f] |
[\p{Z}\t\r\n\v\f] |
[:upper:] |
大寫字母字符 |
[A-Z] |
\p{Lu} |
[:word:]* |
字母字符 |
[A-Za-z0-9_] |
[\p{L}\p{N}\p{Pc}] |
[:xdigit:] |
十六進制字符 |
[A-Fa-f0-9] |
[A-Fa-f0-9] |
注:標記*的字符組簡記法並不是POSIX規范所要求的,但使用很多,一般語言中都提供,文檔中也會出現。
POSIX字符組的使用有所不同。主要區別在於,PCRE字符組簡記法可以脫離方括號直接出現,而POSIX字符組必須出現在方括號內,所以同樣是匹配數字字符,單獨出現時,PCRE中可以直接寫『\d』,而POSIX字符組就必須寫成『[[:digit:]]』。
Linux/Unix下的工具中,一般都可以直接使用POSIX字符組,而PCRE的字符組簡記法『\w』、『\d』等則大多不支持,所以如果你看到『[[:space:]]』而不是『\s』,一定不要感到奇怪。
不過,在常用的編程語言中,Java、PHP、Ruby也支持使用POSIX字符組。其中Java和PHP中的POSIX字符組都是按照ASCII 語言環境進行匹配;
Ruby的情況則要復雜一點,Ruby 1.8按照ASCII語言環境進行匹配,而且不支持『[:word:]』和『[:alnum:]』,Ruby 1.9按照Unicode語言環境進行匹配,同時支持『[:word:]』和『[:alnum:]』。
4)幾種POSIX規范正則表達式的比較
流派 |
說明 |
工具 |
BRE |
(、)、{、}都必須轉義使用,不支持+、?、| |
grep、sed、vi(但vi支持這些多選結構和反向引用) |
GNU BRE |
(、)、{、}、+、?、|都必須轉義使用 |
GNU grep、GNU sed |
ERE |
元字符不必轉義,+、?、(、)、{、}、|可以直接使用,\1、\2的支持不確定 |
egrep、awk |
GNU ERE |
元字符不必轉義,+、?、(、)、{、}、|可以直接使用,支持\1、\2 |
grep –E、GNU awk |
三、對比
由上面兩節我們看到,PCRE和POSIX RE的區別大多在於對元字符的選擇和轉義使用,除此之外他們還在以下幾個方面有所不同:
1)定界符:
POSIX兼容正則沒有定界符,函數的相應參數會被認為是正則。
PERL兼容正則可以使用任何不是字母、數字或反斜線(\)的字符作為定界符,如果作為定界符的字符必須被用在表達式本身中,則需要用反斜線轉義。也可以使用(),{},[] 和 <> 作為定界符
2)修飾符:
POSIX兼容正則沒有修飾符。
PERL兼容正則中可能使用的修飾符(修飾符中的空格和換行被忽略,其它字符會導致錯誤):
i (PCRE_CASELESS):匹配時忽略大小寫。
m(PCRE_MULTILINE):當設定了此修飾符,行起始(^)和行結束($)除了匹配整個字符串開頭和結束外,還分別匹配其中的換行符(\n)的之后和之前。
s(PCRE_DOTALL):如果設定了此修飾符,模式中的圓點元字符(.)匹配所有的字符,包括換行符。沒有此設定的話,則不包括換行符。
x(PCRE_EXTENDED):如果設定了此修飾符,模式中的空白字符除了被轉義的或在字符類中的以外完全被忽略。
A(PCRE_ANCHORED):如果設定了此修飾符,模式被強制為“anchored”,即強制僅從目標字符串的開頭開始匹配。
D(PCRE_DOLLAR_ENDONLY):如果設定了此修飾符,模式中的行結束($)僅匹配目標字符串的結尾。沒有此選項時,如果最后一個字符是換行符的話,也會被匹配在里面。如果設定了 m 修飾符則忽略此選項。
U(PCRE_UNGREEDY):使“?”的默認匹配成為貪婪狀態的。
X(PCRE_EXTRA):模式中的任何反斜線后面跟上一個沒有特殊意義的字母導致一個錯誤,從而保留此組合以備將來擴充。默認情況下,一個反斜線后面跟一個沒有特殊意義的字母被當成該字母本身。
u(PCRE_UTF8):模式字符串被當成UTF-8。
3)關於元字符『.』的解釋:
. PERL兼容正則匹配除了換行符外的任意一個字符
. POSIX兼容正則匹配任意一個字符
最后以常用Linux/Unix工具中的表示法來舉例說明他們之間的轉義情況,其中的工具GNU的版本為准:
PCRE記法 |
vi/vim |
grep |
awk |
sed |
* |
* |
* |
* |
* |
+ |
\+ |
\+ |
+ |
\+ |
? |
\= |
\? |
? |
\? |
{m,n} |
\{m,n} |
\{m,n\} |
{m,n} |
\{m,n\} |
\b * |
\< \> |
\< \> |
\< \> |
\y \< \> |
(…|…) |
\(…\|…\) |
\(…\|…\) |
(…|…) |
(…|…) |
(…) |
\(…\) |
\(…\) |
(…) |
(…) |
\1 \2 |
\1 \2 |
\1 \2 |
不支持 |
\1 \2 |
注:PCRE中常用\b來表示“單詞的起始或結束位置”,但Linux/Unix的工具中,通常用\<來匹配“單詞的起始位置”,用\>來匹配“單詞的結束位置”,sed中的\y可以同時匹配這兩個位置。
參考文章:
http://www.css119.com/book/RegExp/
http://www.chinaunix.net/old_jh/25/159388.html
http://www.infoq.com/cn/news/2011/07/regular-expressions-6-POSIX
http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap09.html