前言 Regular Expressions(正則表達(dá)式,以下用RE稱呼)對(duì)小弟來說一直都是神密的地帶,看到一些網(wǎng)絡(luò)上的大大,簡(jiǎn)單用RE就決解了某些文字的問題,小弟便興起了學(xué)一學(xué)RE的想法,但小弟天生就比較懶一些,總希望看有沒有些快速學(xué)習(xí)的方式,于是小弟又請(qǐng)出Google大神,藉由祂的神力,小弟在網(wǎng)絡(luò)上找到了Jim Hollenhorst先生的文章,經(jīng)過了閱讀,小弟覺得真是不錯(cuò),所以就做個(gè)小心得報(bào)告,跟Move-to.Net的朋友分享,希望能為各位大大帶來一丁點(diǎn)在學(xué)習(xí)RE時(shí)的幫助。Jim Hollenhorst大大文章之網(wǎng)址如下,有需要的大大可直接連結(jié)。 The 30 Minute Regex Tutorial By Jim Hollenhorst http://www.codeproject.com/useritems/RegexTutorial.asp 什么是RE? 想必各位大大在做文件查找的時(shí)侯都有使用過萬用字符”*”,比如說想查找在Windows目錄下所有的Word文件時(shí),你可能就會(huì)用”*.doc”這樣的方式來做查找,因?yàn)椤?”所代表的是任意的字符。RE所做的就是類似這樣的功能,但其功能更為強(qiáng)大。 寫程序時(shí),常需要比對(duì)字符串是否符合特定樣式,RE最主要的功能就是來描述這特定的樣式,因此可以將RE視為特定樣式的描述式,舉個(gè)例子來說,”w+”所代表的就是任何字母與數(shù)字所組成的非空字符串(non-null string)。在.NET framework中提供了非常強(qiáng)大的類別庫,藉此可以很輕易的使用RE來做文字的查找與取代、對(duì)復(fù)雜標(biāo)頭的譯碼及驗(yàn)證文字等工作。 學(xué)習(xí)RE最好的方式就是藉由例子親自來做做看。Jim Hollenhorst大大也提供了一個(gè)工具程序Expresso(來杯咖啡吧),來幫助我們學(xué)習(xí)RE,下載的網(wǎng)址是http://www.codeproject.com/useritems/RegexTutorial/ExpressoSetup2_1C.zip。 接下來,就讓我們來體驗(yàn)一些例子吧。 一些簡(jiǎn)單的例子 假設(shè)要查找文章中Elvis后接有alive的文字符串的話,使用RE可能會(huì)經(jīng)過下列的過程,括號(hào)是所下RE的意思: 1. elvis (查找elvis) 上述代表所要查找的字符順序?yàn)閑lvis。在.NET中可以設(shè)定乎略字符的大小寫,所以”Elvis”、”ELVIS”或者是”eLvIs”都是符合1所下的RE。但因?yàn)檫@只管字符出現(xiàn)的順序?yàn)閑lvis,所以pelvis也是符合1所下的RE。可以用2的RE來改進(jìn)。 2. belvisb (將elvis視為一整體的字查找,如elvis、Elvis乎略字符大小寫時(shí)) “b”在RE中有特別的意思,在上述的例子中所指的就是字的邊界,所以belvisb用b把elvis的前后邊界界定出來,也就是要elvis這個(gè)字。 假設(shè)要將同一行里elvis后接有alive的文字符串找出來,此時(shí)就會(huì)用到另外二個(gè)特別意義的字符”.”及”*”。”.”所代表就是除了換行字符的任意字符,而”*”所代表的是重復(fù)*之前項(xiàng)目直到找到符合RE的字符串。所以”.*”所指的就是除了換行字符外的任意數(shù)目的字符數(shù)。所以查找同一行里elvis后接有alive的文字符串找出來,則可下如3之RE。 3. belvisb.*baliveb (查找elvis后面接有alive的文字符串,如elvis is alive) 用簡(jiǎn)單之特別字符就可以組成功能強(qiáng)大的RE,但也發(fā)現(xiàn)當(dāng)使用越來越多的特別字符時(shí),RE就會(huì)越來越難看得懂了。 再看看另外的例子 組成有效的電話號(hào)碼 假使要從網(wǎng)頁上收集顧客格式為xxx-xxxx的7位數(shù)字的電話號(hào)碼,其中x是數(shù)字,RE可能會(huì)這樣寫。 4. bddd-dddd (查找七位數(shù)字之電話號(hào)碼,如123-1234) 每一個(gè)d代表一個(gè)數(shù)字。”-”則是一般的連字符號(hào),為避免太多重復(fù)的d,RE可以改寫成如5的方式。 5. bd{3}-d{4} (查找七位數(shù)字電話號(hào)碼較好的方法,如123-1234) 在d后的{3},代表重復(fù)前一個(gè)項(xiàng)目三次,也就是相等于ddd。 RE的學(xué)習(xí)及測(cè)試工具 Expresso 因?yàn)镽E不易閱讀及使用者容易會(huì)下錯(cuò)RE的特性,Jim大大開發(fā)了一個(gè)工具軟件Expresso,用來幫助使用者學(xué)習(xí)及測(cè)試RE,除了上面所述的網(wǎng)址之外,也可以上Ultrapico網(wǎng)站(http://www.Ultrapico.com)。安裝完Expresso后,在Expression Library中,Jim大大把文章的例子都建立在其中,可以邊看文章邊測(cè)試,也可以試著修改范例所下的RE,馬上可以看到結(jié)果,小弟覺得非常好用。各位大大可以試試。 .NET中RE的基礎(chǔ)概念 特殊字符 有些字符有特別的意義,比如之前所看到的”b”、”.”、”*”、”d”等。”s”所代表的是任意空格符,比如說spaces、tabs、newlines等.。”w”代表是任意字母或數(shù)字字符。 再看一些例子吧 6. baw*b (查找a開頭的字,如able) 這RE描述要查找一個(gè)字的開始邊界(b),再來是字母”a”,再加任意數(shù)目的字母數(shù)字(w*),再接結(jié)束這個(gè)字的結(jié)束邊界(b)。 7. d+ (查找數(shù)字字符串) “+”和”*”非常相似,除了+至少要重復(fù)前面的項(xiàng)目一次。也就是說至少有一個(gè)數(shù)字。 8. bw{6}b (查找六個(gè)字母數(shù)字的字,如ab123c) 下表為RE常用的特殊字符 . 除了換行字符的任意字符 w 任意字母數(shù)字字符 s 任意空格符 d 任意數(shù)字字符 b 界定字的邊界 ^ 文章的開頭,如”^The'' 用以表示出現(xiàn)于文章開頭的字符串為”The” $ 文章的結(jié)尾,如”End$”用以表示出現(xiàn)在文章的結(jié)尾為”End” 特殊字符”^”及”$”是用來查找某些字必需是文章的開頭或結(jié)尾,這在驗(yàn)證輸入是否符合某一樣式時(shí)特別用有,比如說要驗(yàn)證七位數(shù)字的電話號(hào)碼,可能會(huì)輸入如下9的RE。 9. ^d{3}-d{4}$ (驗(yàn)證七位數(shù)字之電話號(hào)碼) 這和第5個(gè)RE相同,但其前后都無其它的字符,也就是整串字符串只有這七個(gè)數(shù)字的電話號(hào)碼。在.NET中如果設(shè)定Multiline這個(gè)選項(xiàng),則”^”和”$”會(huì)每行進(jìn)行比較,只要某行的開頭結(jié)尾符合RE即可,而不是整個(gè)文章字符串做一次比較。 轉(zhuǎn)意字符(Escaped characters) 有時(shí)可能會(huì)需要”^”、”$”單純的字面意義(literal meaning)而不要將它們當(dāng)成特殊字符,此時(shí)””字符就是用來移除特殊字符特別意義的字符,因此”^”、”.”、””所代表的就是”^”、”.”、””的字面意義。 重復(fù)前述項(xiàng)目 在前面看過”{3}”及”*”可以用來重復(fù)前述字符,之后我們會(huì)看到如何用同樣的語法重復(fù)整個(gè)次描述(subexpressions)。下表是使用重復(fù)前述項(xiàng)目的一些方式。 * 重復(fù)任意次數(shù) + 重復(fù)至少一次 ? 重復(fù)零次或一次 {n} 重復(fù)n次 {n,m} 重復(fù)至少n次,但不超過m次 {n,} 重復(fù)至少n次 再來試一些例子吧 10. bw{5,6}b (查找五個(gè)或六個(gè)字母數(shù)字字符的字,如as25d、d58sdf等) 11. bd{3}sd{3}-d{4} (查找十個(gè)數(shù)字的電話號(hào)碼,如800 123-1234) 12. d{3}-d{2}-d{4} (查找社會(huì)保險(xiǎn)號(hào)碼,如 123-45-6789) 13. ^w* (每行或整篇文章的第一個(gè)字) 在Espresso可試試有Multiline和沒Multiline的不同。 匹配某范圍的字符 有時(shí)需要查找某些特定的字符時(shí)怎么辨?這時(shí)中括號(hào)”[]”就派上了用場(chǎng)。因此[aeiou]所要查找的是”a”、”e”、”i”、”o”、”u”這些元音,[.?!]所要查找的是”.”、”?”、”!”這些符號(hào),在中括號(hào)中的特殊字符的特別意義都會(huì)被移除,也就是解譯成單純的字面意義。也可以指定某些范圍的字符,如”[a-z0-9]”,所指的就是任意小寫字母或任意數(shù)字。 接下來再看一個(gè)比較初復(fù)雜查找電話號(hào)碼的RE例子 14. (?d{3}[( ] s?d{3}[- ]d{4} (查找十位數(shù)字之電話號(hào)碼,如(080) 333-1234 ) 這樣的RE可查找出較多種格式的電話號(hào)碼,如(080) 123-4567、511 254 6654等。”(?”代表一個(gè)或零個(gè)左小括號(hào)”(“,而”[( ]”代表查找一個(gè)右小括號(hào)”)”或空格符,”s?”指一個(gè)或零個(gè)空格符組。但這樣的RE會(huì)將類似”800) 45-3321”這樣的電話找出來,也就是括號(hào)沒有對(duì)稱平衡的問題,之后會(huì)學(xué)到擇一(alternatives)來決解這樣的問題。 不包含在某特定字符組里(Negation) 有時(shí)需要查找在包含在某特定字符組里的字符,下表說明如何做類似這樣的描述。 W 不是字母數(shù)字的任意字符 S 不是空格符的任意字符 D 不是數(shù)字字符的任意字符 B 不在字邊界的位置 [^x] 不是x的任意字符 [^aeiou] 不是a、e、i、o、u的任意字符 15. S+ (不包含空格符的字符串) 擇一(Alternatives) 有時(shí)會(huì)需要查找?guī)讉€(gè)特定的選擇,此時(shí)”|”這個(gè)特殊字符就派上用場(chǎng)了,舉例來說,要查找五個(gè)數(shù)字及九個(gè)數(shù)字(有”-”號(hào))的郵政編碼。 16. bd{5}-d{4}b|bd{5}b (查找五個(gè)數(shù)字及九個(gè)數(shù)字(有”-”號(hào))的郵政編碼) 在使用Alternatives時(shí)需要注意的是前后的次序,因?yàn)镽E在Alternatives中會(huì)優(yōu)先選擇符合最左邊的項(xiàng)目,16中,如果把查找五個(gè)數(shù)字的項(xiàng)目放在前面,則這RE只會(huì)找到五個(gè)數(shù)字的郵政編碼。了解了擇一,可將14做更好的修正。 17. ((d{3})|d{3})s?d{3}[- ]d{4} (十個(gè)數(shù)字的電話號(hào)碼) 群組(Grouping) 括號(hào)可以用來介定一個(gè)次描述,經(jīng)由次描述的介定,可以針對(duì)次描述做重復(fù)或及他的處理。 18. (d{1,3}.){3}d{1,3} (尋找網(wǎng)絡(luò)地址的簡(jiǎn)單RE) 此RE的意思第一個(gè)部分(d{1,3}.){3},所指的是,數(shù)字最小一位最多三位,并且后面接有”.”符號(hào),此類型的共有三個(gè),之后再接一到三位的數(shù)字,也就是如192.72.28.1這樣的數(shù)字。 但這樣會(huì)有個(gè)缺點(diǎn),因?yàn)榫W(wǎng)絡(luò)地址數(shù)字最多只到255,但上述的RE只要是一到三位的數(shù)字都是符合的,所以這需要讓比較的數(shù)字小于256才行,但只單獨(dú)使用RE并無法做這樣的比較。在19中使用擇一來將地址的限制在所需要的范圍內(nèi),也就是0到255。 19. ((2[0-4]d|25[0-5]|[01]?dd?).){3}(2[0-4]d|25[0-5]|[01]?dd?) (尋找網(wǎng)絡(luò)地址) 有沒有發(fā)覺RE越來越像外星人說的話了?就以簡(jiǎn)單的尋找網(wǎng)絡(luò)地址,直接看RE都滿難理解的哩。 Expresso Analyzer View Expresso提供了一個(gè)功能,它可以將所下的RE變成樹狀的說明,一組組的分開說明,提供了一個(gè)好的除錯(cuò)環(huán)境。其它的功能,如部分符合(Partial Match只查找反白R(shí)E的部分)及除外符合(Exclude Match只不查找反白R(shí)E的部分)就留給各位大大試試啰。 當(dāng)次描述用括號(hào)群組起來時(shí),符合次描述的文字可用在之后的程序處理或RE本身。在預(yù)設(shè)的情型下,所符合的群組是由數(shù)字命名,由1開始,由順序是由左至右,這自動(dòng)群組命名,可在Expresso中的skeleton view或result view中看到。 Backreference是用來查找群組中抓取的符合文字所相同的文字。舉例來說”1”所指符合群組1所抓取的文字。 20. b(w+)bs*1b (尋找重復(fù)字,此處說的重復(fù)是指同樣的字,中間有空白隔開如dog dog這樣的字) (w+)會(huì)抓取至少一個(gè)字符的字母或數(shù)字的字,并將它命名為群組1,之后是查找任意空格符,再接和群組1相同的文字。 如果不喜歡群組自動(dòng)命名的1,也可以自行命名,以上述例子為例,(w+)改寫為(?<Word>w+),這就是將所抓取的群組命名為Word,Backreference就要改寫成為k<Word> 21. b(?<Word>w+)bs*k<Word>b (使用自行命名群組抓取重復(fù)字) 使用括號(hào)還有許多特別的語法元素,比較通用的列表如下: 抓取(Captures) (exp) 符合exp并抓取它進(jìn)自動(dòng)命名的群組 (?<name>exp) 符合exp并抓取它進(jìn)命名的群組name (?:exp) 符合exp,不抓取它 Lookarounds (?=exp) 符合字尾為exp的文字 (?<=exp) 符合前綴為exp的文字 (?!exp) 符合后面沒接exp字尾的文字 (?<!exp) 符合前面沒接exp前綴的文字 批注Comment (?#comment) 批注 Positive Lookaround 接下來要談的是lookahead及l(fā)ookbehind assertions。它們所查找的是目前符合之前或之后的文字,并不包含目前符合本身。這些就如同”^”及”b”特殊字符,本身并不會(huì)對(duì)應(yīng)任何文字(用來界定位置),也因此稱做是zero-width assertions,看些例子也許會(huì)清楚些。 (?=exp)是一個(gè)”zero-width positive lookahead assertion”。它指的就是符合字尾為exp的文字,但不包含exp本身。 22. bw+(?=ingb) (字尾為ing的字,比如說filling所符合的就是fill) (?<=exp)是一個(gè)”zero-width positive lookbehind assertion”。它指的就是符合前綴為exp的文字,但不包含exp本身。 23. (?<=bre)w+b (前綴為re的字,比如說repeated所符合的就是peated) 24. (?<=d)d{3}b (在字尾的三位數(shù)字,且之前接一位數(shù)字) 25. (?<=s)w+(?=s) (由空格符分隔開的字母數(shù)字字符串) Negative Lookaround 之前有提到,如何查找一個(gè)非特定或非在特定群組的字符。但如果只是要驗(yàn)證某字符不存在而不要對(duì)應(yīng)這些字符進(jìn)來呢?舉個(gè)例子來說,假設(shè)要查找一個(gè)字,它的字母里有q但接下來的字母不是u,可以用下列的RE來做。 26. bw*q[^u]w*b (一個(gè)字,其字母里有q但接下來的字母不是u) 這樣的RE會(huì)有一個(gè)問題,因?yàn)閇^u]要對(duì)應(yīng)一個(gè)字符,所以若q是字的最后一個(gè)字母,[^u]這樣的下法就會(huì)將空格符對(duì)應(yīng)下去,結(jié)果就有可能會(huì)符合二個(gè)字,比如說”Iraq haha”這樣的文字。使用Negative Lookaround就能解決這樣的問題。 27. bw*q(?!u)w*b (一個(gè)字,其字母里有q但接下來的字母不是u) 這是”zero-width negative lookahead assertion”。 28. d{3}(?!d) (三個(gè)位的數(shù)字,其后不接一個(gè)位數(shù)字) 同樣的,可以使用(?<!exp),”zero-width negative lookbehind assertion”,來符合前面沒接exp前綴的文字符串。 29. (?<![a-z ])w{7} (七個(gè)字母數(shù)字的字符串,其前面沒接字母或空格) 30. (?<=<(w+)>).*(?=</1>) (HTML卷標(biāo)間的文字) 這使用lookahead及l(fā)ookbehind assertion來取出HTML間的文字,不包括HTML卷標(biāo)。 請(qǐng)批注(Comments Please) 括號(hào)還有個(gè)特殊的用途就是用來包住批注,語法為”(?#comment)”,若設(shè)定”Ignore Pattern Whitespace”選項(xiàng),則RE中的空格符當(dāng)RE使用時(shí)會(huì)乎略。此選項(xiàng)設(shè)定時(shí),”#”之后的文字會(huì)乎略。 31. HTML卷標(biāo)間的文字,加上批注 (?<= #查找前綴,但不包含它 <(w+)> #HTML標(biāo)簽 ) #結(jié)束查找前綴 .* #符合任何文字 (?= #查找字尾,但不包含它 </1> #符合所抓取群組1之字符串,也就是前面小括號(hào)的HTML標(biāo)簽 ) #結(jié)束查找字尾 尋找最多字符的字及最少字符的字(Greedy and Lazy) 當(dāng)RE下要查找一個(gè)范圍的重復(fù)時(shí)(如”.*”),它通常會(huì)尋找最多字符的符合字,也就是Greedy matching。舉例來說。 32. a.*b (開始為a結(jié)束為b的最多字符的符合字) 若有一字符串是”aabab”,使用上述RE所得到的符合字符串就是”aabab”,因?yàn)檫@是尋找最多字符的字。有時(shí)希望是符合最少字符的字也就是lazy matching。只要將重復(fù)前述項(xiàng)目的表加上問號(hào)(?)就可以把它們?nèi)孔兂蒷azy matching。因此”*?”代表的就是重復(fù)任意次數(shù),但是使用最少重復(fù)的次數(shù)來符合。舉個(gè)例子來說: 33. a.*?b (開始為a結(jié)束為b的最少字符的符合字) 若有一字符串是”aabab”,使用上述RE第一個(gè)所得到的符合字符串就是”aab”再來是”ab”,因?yàn)檫@是尋找最少字符的字。 *? 重復(fù)任意次數(shù),最少重復(fù)次數(shù)為原則 +? 重復(fù)至少一次,最少重復(fù)次數(shù)為原則 ?? 重復(fù)零次或一次,最少重復(fù)次數(shù)為原則 {n,m}? 重復(fù)至少n次,但不超過m次,最少重復(fù)次數(shù)為原則 {n,}? 重復(fù)至少n次,最少重復(fù)次數(shù)為原則 還有什么沒提到呢? 到目前為止,已經(jīng)提到了許多建立RE的元素,當(dāng)然還有許多元素沒有提到,下表整理了一些沒提到的元素,在最左邊的字段的數(shù)字是說明在Expresso中的例子。 # 語法 說明 a Bell 字符 b 通常是指字的邊界,在字符組里所代表的就是backspace t Tab 34 r Carriage return v Vertical Tab f From feed 35 n New line e Escape 36 nnn ASCII八位碼為nnn的字符 37 xnn 十六位碼為nn的字符 38 unnnn Unicode為nnnn的字符 39 cN Control N字符,舉例來說Ctrl-M是cM 40 A 字符串的開始(和^相似,但不需籍由multiline選項(xiàng)) 41 Z 字符串的結(jié)尾 z 字符串的結(jié)尾 42 G 目前查找的開始 43 p{name} Unicode 字符組名稱為name的字符,比如說p{Lowercase_Letter} 所指的就是小寫字 (?>exp) Greedy次描述,又稱之為non-backtracking次描述。這只符合一次且不采backtracking。 44 (?<x>-<y>exp) or (?-<y>exp) 平衡群組。雖復(fù)雜但好用。它讓已命名的抓取群組可以在堆棧中操作使用。(小弟對(duì)這個(gè)也是不太懂哩) 45 (?im-nsx:exp) 為次描述exp更改RE選項(xiàng),比如(?-i:Elvis)就是把Elvis大乎略大小寫的選項(xiàng)關(guān)掉 46 (?im-nsx) 為之后的群組更改RE選項(xiàng)。 (?(exp)yes|no) 次描述exp視為zero-width positive lookahead。若此時(shí)有符合,則yes次描述為下一個(gè)符合標(biāo)的,若否,則no 次描述為下一個(gè)符合標(biāo)的。 (?(exp)yes) 和上述相同但無no次描述 (?(name)yes|no) 若name群組為有效群組名稱,則yes次描述為下一個(gè)符合標(biāo)的,若否,則no 次描述為下一個(gè)符合標(biāo)的。 47 (?(name)yes) 和上述相同但無no次描述 結(jié)論 經(jīng)過了一連串的例子,及Expresso的幫忙,相信各位大大對(duì)RE有個(gè)基本的了解,網(wǎng)絡(luò)上當(dāng)然有許多有關(guān)于RE的文章,如果各位大大有興趣http://www.codeproject.com 還有許多關(guān)于RE的相關(guān)文章。若大大對(duì)書有興趣的話,Jeffrey Friedl的Mastering Regular Expressions很多大大都有推(小弟還沒拜讀)。希望籍由這樣的心得報(bào)告,能讓對(duì)RE有興趣的大大能縮短學(xué)習(xí)曲線,當(dāng)然這是小弟第一次接觸RE,若文章中有什么錯(cuò)誤或說明的不好的地方,可要請(qǐng)各位大大體諒,并請(qǐng)各位大大將需要修正的地方mail給小弟,小弟會(huì)非常感謝各位大大。 |