前言
Regular Expressions(以下用RE稱呼)對小弟來說一直都是神密的地帶,看到一些網路上的大大,簡單用RE就決解了某些文字的問題,小弟便興起了學一學RE的想法,但小弟天生就比較懶一些,總希望看有沒有些快速學習的方式,於是小弟又請出Google大神,藉由祂的神力,小弟在網路上找到了Jim Hollenhorst先生的文章,經過了閱讀,小弟覺得真是不錯,所以就做個小心得報告,跟Move-to.Net的朋友分享,希望能為各位大大帶來一丁點在學習RE時的幫助。Jim Hollenhorst大大文章之網址如下,有需要的大大可直接連結。
The 30 Minute Regex Tutorial By Jim Hollenhorst
http://www.codeproject.com/useritems/RegexTutorial.asp
什麼是RE?
想必各位大大在做檔案搜尋的時侯都有使用過萬用字元”*”,比如說想搜尋在Windows目錄下所有的Word檔案時,你可能就會用”*.doc”這樣的方式來做搜尋,因為”*”所代表的是任意的字元。RE所做的就是類似這樣的功能,但其功能更為強大。
寫程式時,常需要比對字串是否符合特定樣式,RE最主要的功能就是來描述這特定的樣式,因此可以將RE視為特定樣式的描述式,舉個例子來說,”\w+”所代表的就是任何字母與數字所組成的非空字串(non-null string)。在.NET framework中提供了非常強大的類別庫,藉此可以很輕易的使用RE來做文字的搜尋與取代、對複雜標頭的解碼及驗証文字等工作。
學習RE最好的方式就是藉由例子親自來做做看。Jim Hollenhorst大大也提供了一個工具程式Expresso(來杯咖啡吧),來幫助我們學習RE,下載的網址是
http://www.codeproject.com/useritems/RegexTutorial/ExpressoSetup2_1C.zip。
接下來,就讓我們來體驗一些例子吧。
一些簡單的例子
假設要搜尋文章中Elvis後接有alive的文字串的話,使用RE可能會經過下列的過程,括號是所下RE的意思:
1. elvis (搜尋elvis)
上述代表所要搜尋的字元順序為elvis。在.NET中可以設定乎略字元的大小寫,所以”Elvis”、”ELVIS”或者是”eLvIs”都是符合1所下的RE。但因為這只管字元出現的順序為elvis,所以pelvis也是符合1所下的RE。可以用2的RE來改進。
2. \belvis\b (將elvis視為一整體的字搜尋,如elvis、Elvis乎略字元大小寫時)
“\b”在RE中有特別的意思,在上述的例子中所指的就是字的邊界,所以\belvis\b用\b把elvis的前後邊界界定出來,也就是要elvis這個字。
假設要將同一行裏elvis後接有alive的文字串找出來,此時就會用到另外二個特別意義的字元”.”及”*”。”.”所代表就是除了換行字元的任意字元,而”*”所代表的是重覆*之前項目直到找到符合RE的字串。所以”.*”所指的就是除了換行字元外的任意數目的字元數。所以搜尋同一行裏elvis後接有alive的文字串找出來,則可下如3之RE。
3. \belvis\b.*\balive\b (搜尋elvis後面接有alive的文字串,如elvis is alive)
用簡單之特別字元就可以組成功能強大的RE,但也發現當使用愈來愈多的特別字元時,RE就會愈來愈難看得懂了。
再看看另外的例子
組成有效的電話號碼
假使要從網頁上收集顧客格式為xxx-xxxx的7位數字的電話號碼,其中x是數字,RE可能會這樣寫。
4. \b\d\d\d-\d\d\d\d (搜尋七位數字之電話號碼,如123-1234)
每一個\d代表一個數字。”-”則是一般的連字符號,為避免太多重覆的\d,RE可以改寫成如5的方式。
5. \b\d{3}-\d{4} (搜尋七位數字電話號碼較好的方法,如123-1234)
在\d後的{3},代表重覆前一個項目三次,也就是相等於\d\d\d。
RE的學習及測試工具 Expresso

因為RE不易閱讀及使用者容易會下錯RE的特性,Jim大大開發了一個工具軟體Expresso,用來幫助使用者學習及測試RE,除了上面所述的網址之外,也可以上Ultrapico網站(http://www.Ultrapico.com)。安裝完Expresso後,在Expression Library中,Jim大大把文章的例子都建立在其中,可以邊看文章邊測試,也可以試著修改範例所下的RE,馬上可以看到結果,小弟覺得非常好用。各位大大可以試試。
.NET中RE的基礎概念
特殊字元
有些字元有特別的意義,比如之前所看到的”\b”、”.”、”*”、”\d”等。”\s”所代表的是任意空白字元,比如說spaces、tabs、newlines等.。”\w”代表是任意字母或數字字元。
再看一些例子吧
6. \ba\w*\b (搜尋a開頭的字,如able)
這RE描述要搜尋一個字的開始邊界(\b),再來是字母”a”,再加任意數目的字母數字(\w*),再接結束這個字的結束邊界(\b)。
7. \d+ (搜尋數字字串)
“+”和”*”非常相似,除了+至少要重覆前面的項目一次。也就是說至少有一個數字。
8. \b\w{6}\b (搜尋六個字母數字的字,如ab123c)
下表為RE常用的特殊字元
. 除了換行字元的任意字元
\w 任意字母數字字元
\s 任意空白字元
\d 任意數字字元
\b 界定字的邊界
^ 文章的開頭,如”^The'' 用以表示出現於文章開頭的字串為”The”
$ 文章的結尾,如”End$”用以表示出現在文章的結尾為”End”
特殊字元”^”及”$”是用來搜尋某些字必需是文章的開頭或結尾,這在驗証輸入是否符合某一樣式時特別用有,比如說要驗証七位數字的電話號碼,可能會輸入如下9的RE。
9. ^\d{3}-\d{4}$ (驗証七位數字之電話號碼)
這和第5個RE相同,但其前後都無其他的字元,也就是整串字串只有這七個數字的電話號碼。在.NET中如果設定Multiline這個選項,則”^”和”$”會每行進行比較,只要某行的開頭結尾符合RE即可,而不是整個文章字串做一次比較。
跳脫字元(Escaped characters)
有時可能會需要”^”、”$”單純的字面意義(literal meaning)而不要將它們當成特殊字元,此時”\”字元就是用來移除特殊字元特別意義的字元,因此”\^”、”\.”、”\\”所代表的就是”^”、”.”、”\”的字面意義。
重覆前述項目
在前面看過”{3}”及”*”可以用來重覆前述字元,之後我們會看到如何用同樣的語法重覆整個次描述(subexpressions)。下表是使用重覆前述項目的一些方式。
* 重覆任意次數
+ 重覆至少一次
? 重覆零次或一次
{n} 重覆n次
{n,m} 重覆至少n次,但不超過m次
{n,} 重覆至少n次
再來試一些例子吧
10. \b\w{5,6}\b (搜尋五個或六個字母數字字元的字,如as25d、d58sdf等)
11. \b\d{3}\s\d{3}-\d{4} (搜尋十個數字的電話號碼,如800 123-1234)
12. \d{3}-\d{2}-\d{4} (搜尋社會保險號碼,如 123-45-6789)
13. ^\w* (每行或整篇文章的第一個字)
在Espresso可試試有Multiline和沒Multiline的不同。
匹配某範圍的字元
有時需要搜尋某些特定的字元時怎麼辨?這時中括號”[]”就派上了用場。因此[aeiou]所要搜尋的是”a”、”e”、”i”、”o”、”u”這些母音,[.?!]所要搜尋的是”.”、”?”、”!”這些符號,在中括號中的特殊字元的特別意義都會被移除,也就是解譯成單純的字面意義。也可以指定某些範圍的字元,如”[a-z0-9]”,所指的就是任意小寫字母或任意數字。
接下來再看一個比較初複雜搜尋電話號碼的RE例子
14. \(?\d{3}[( ] \s?\d{3}[- ]\d{4} (搜尋十位數字之電話號碼,如(080) 333-1234 )
下這樣的RE可搜尋出較多種格式的電話號碼,如(080) 123-4567、511 254 6654等。”\(?”代表一個或零個左小括號”(“,而”[( ]”代表搜尋一個右小括號”)”或空白字元,”\s?”指一個或零個空白字元組。但這樣的RE會將類似”800) 45-3321”這樣的電話找出來,也就是括號沒有對稱平衡的問題,之後會學到擇一(alternatives)來決解這樣的問題。
不包含在某特定字元組裏(Negation)
有時需要搜尋在包含在某特定字元組裏的字元,下表說明如何做類似這樣的描述。
\W 不是字母數字的任意字元
\S 不是空白字元的任意字元
\D 不是數字字元的任意字元
\B 不在字邊界的位置
[^x] 不是x的任意字元
[^aeiou] 不是a、e、i、o、u的任意字元
15. \S+ (不包含空白字元的字串)
擇一(Alternatives)
有時會需要搜尋幾個特定的選擇,此時”|”這個特殊字元就派上用場了,舉例來說,要搜尋五個數字及九個數字(有”-”號)的郵遞區號。
16. \b\d{5}-\d{4}\b|\b\d{5}\b (搜尋五個數字及九個數字(有”-”號)的郵遞區號)
在使用Alternatives時需要注意的是前後的次序,因為RE在Alternatives中會優先選擇符合最左邊的項目,16中,如果把搜尋五個數字的項目放在前面,則這RE只會找到五個數字的郵遞區號。了解了擇一,可將14做更好的修正。
17. (\(\d{3}\)|\d{3})\s?\d{3}[- ]\d{4} (十個數字的電話號碼)
群組(Grouping)
括號可以用來介定一個次描述,經由次描述的介定,可以針對次描述做重覆或及他的處理。
18. (\d{1,3}\.){3}\d{1,3} (尋找網路位址的簡單RE)
此RE的意思第一個部分(\d{1,3}\.){3},所指的是,數字最小一位最多三位,並且後面接有”.”符號,此類型的共有三個,之後再接一到三位的數字,也就是如192.72.28.1這樣的數字。
但這樣會有個缺點,因為網路位址數字最多只到255,但上述的RE只要是一到三位的數字都是符合的,所以這需要讓比較的數字小於256才行,但只單獨使用RE並無法做這樣的比較。在19中使用擇一來將位址的限制在所需要的範圍內,也就是0到255。
19. ((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?) (尋找網路位址)
有沒有發覺RE愈來愈像外星人說的話了?就以簡單的尋找網路位址,直接看RE都滿難理解的哩。
Expresso Analyzer View

Expresso提供了一個功能,它可以將所下的RE變成樹狀的說明,一組組的分開說明,提供了一個好的除錯環境。其它的功能,如部分符合(Partial Match只搜尋反白RE的部分)及除外符合(Exclude Match只不搜尋反白RE的部分)就留給各位大大試試囉。
當次描述用括號群組起來時,符合次描述的文字可用在之後的程式處理或RE本身。在預設的情型下,所符合的群組是由數字命名,由1開始,由順序是由左至右,這自動群組命名,可在Expresso中的skeleton view或result view中看到。
Backreference是用來搜尋群組中抓取的符合文字所相同的文字。舉例來說”\1”所指符合群組1所抓取的文字。
20. \b(\w+)\b\s*\1\b (尋找重複字,此處說的重複是指同樣的字,中間有空白隔開如dog dog這樣的字)
(\w+)會抓取至少一個字元的字母或數字的字,並將它命名為群組1,之後是搜尋任意空白字元,再接和群組1相同的文字。
如果不喜歡群組自動命名的1,也可以自行命名,以上述例子為例,(\w+)改寫為(?<Word>\w+),這就是將所抓取的群組命名為Word,Backreference就要改寫成為\k<Word>
21. \b(?<Word>\w+)\b\s*\k<Word>\b (使用自行命名群組抓取重複字)
使用括號還有許多特別的語法元素,比較通用的列表如下:
抓取(Captures)
(exp) 符合exp並抓取它進自動命名的群組
(?<name>exp) 符合exp並抓取它進命名的群組name
(?:exp) 符合exp,不抓取它
Lookarounds
(?=exp) 符合字尾為exp的文字
(?<=exp) 符合字首為exp的文字
(?!exp) 符合後面沒接exp字尾的文字
(?<!exp) 符合前面沒接exp字首的文字
註解Comment
(?#comment) 註解
Positive Lookaround
接下來要談的是lookahead及lookbehind assertions。它們所搜尋的是目前符合之前或之後的文字,並不包含目前符合本身。這些就如同”^”及”\b”特殊字元,本身並不會對應任何文字(用來界定位置),也因此稱做是zero-width assertions,看些例子也許會清楚些。
(?=exp)是一個”zero-width positive lookahead assertion”。它指的就是符合字尾為exp的文字,但不包含exp本身。
22. \b\w+(?=ing\b) (字尾為ing的字,比如說filling所符合的就是fill)
(?<=exp)是一個”zero-width positive lookbehind assertion”。它指的就是符合字首為exp的文字,但不包含exp本身。
23. (?<=\bre)\w+\b (字首為re的字,比如說repeated所符合的就是peated)
24. (?<=\d)\d{3}\b (在字尾的三位數字,且之前接一位數字)
25. (?<=\s)\w+(?=\s) (由空白字元分隔開的字母數字字串)
Negative Lookaround
之前有提到,如何搜尋一個非特定或非在特定群組的字元。但如果只是要驗証某字元不存在而不要對應這些字元進來呢?舉個例子來說,假設要搜尋一個字,它的字母裏有q但接下來的字母不是u,可以用下列的RE來做。
26. \b\w*q[^u]\w*\b (一個字,其字母裏有q但接下來的字母不是u)
這樣的RE會有一個問題,因為[^u]要對應一個字元,所以若q是字的最後一個字母,[^u]這樣的下法就會將空白字元對應下去,結果就有可能會符合二個字,比如說”Iraq haha”這樣的文字。使用Negative Lookaround就能解決這樣的問題。
27. \b\w*q(?!u)\w*\b (一個字,其字母裏有q但接下來的字母不是u)
這是”zero-width negative lookahead assertion”。
28. \d{3}(?!\d) (三個位元的數字,其後不接一個位元數字)
同樣的,可以使用(?<!exp),”zero-width negative lookbehind assertion”,來符合前面沒接exp字首的文字串。
29. (?<![a-z ])\w{7} (七個字母數字的字串,其前面沒接字母或空格)
30. (?<=<(\w+)>).*(?=<\/\1>) (HTML標籤間的文字)
這使用lookahead及lookbehind assertion來取出HTML間的文字,不包括HTML標籤。
請註解(Comments Please)
括號還有個特殊的用途就是用來包住註解,語法為”(?#comment)”,若設定”Ignore Pattern Whitespace”選項,則RE中的空白字元當RE使用時會乎略。此選項設定時,”#”之後的文字會乎略。
31. HTML標籤間的文字,加上註解
(?<= #搜尋字首,但不包含它
<(\w+)> #HTML標籤
) #結束搜尋字首
.* #符合任何文字
(?= #搜尋字尾,但不包含它
<\/\1> #符合所抓取群組1之字串,也就是前面小括號的HTML標籤
) #結束搜尋字尾
尋找最多字元的字及最少字元的字(Greedy and Lazy)
當RE下要搜尋一個範圍的重複時(如”.*”),它通常會尋找最多字元的符合字,也就是Greedy matching。舉例來說。
32. a.*b (開始為a結束為b的最多字元的符合字)
若有一字串是”aabab”,使用上述RE所得到的符合字串就是”aabab”,因為這是尋找最多字元的字。有時希望是符合最少字元的字也就是lazy matching。只要將重覆前述項目的表加上問號(?)就可以把它們全部變成lazy matching。因此”*?”代表的就是重覆任意次數,但是使用最少重覆的次數來符合。舉個例子來說:
33. a.*?b (開始為a結束為b的最少字元的符合字)
若有一字串是”aabab”,使用上述RE第一個所得到的符合字串就是”aab”再來是”ab”,因為這是尋找最少字元的字。
*? 重覆任意次數,最少重覆次數為原則
+? 重覆至少一次,最少重覆次數為原則
?? 重覆零次或一次,最少重覆次數為原則
{n,m}? 重覆至少n次,但不超過m次,最少重覆次數為原則
{n,}? 重覆至少n次,最少重覆次數為原則
還有什麼沒提到呢?
到目前為止,已經提到了許多建立RE的元素,當然還有許多元素沒有提到,下表整理了一些沒提到的元素,在最左邊的欄位的數字是說明在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選項)
41 \Z 字串的結尾
\z 字串的結尾
42 \G 目前搜尋的開始
43 \p{name} Unicode 字元組名稱為name的字元,比如說\p{Lowercase_Letter} 所指的就是小寫字
(?>exp) Greedy次描述,又稱之為non-backtracking次描述。這只符合一次且不採backtracking。
44 (?<x>-<y>exp)
or (?-<y>exp) 平衡群組。雖複雜但好用。它讓已命名的抓取群組可以在堆疊中操作使用。(小弟對這個也是不太懂哩)
45 (?im-nsx:exp) 為次描述exp更改RE選項,比如(?-i:Elvis)就是把Elvis大乎略大小寫的選項關掉
46 (?im-nsx) 為之後的群組更改RE選項。
(?(exp)yes|no) 次描述exp視為zero-width positive lookahead。若此時有符合,則yes次描述為下一個符合標的,若否,則no 次描述為下一個符合標的。
(?(exp)yes) 和上述相同但無no次描述
(?(name)yes|no) 若name群組為有效群組名稱,則yes次描述為下一個符合標的,若否,則no 次描述為下一個符合標的。
47 (?(name)yes) 和上述相同但無no次描述
結論
經過了一連串的例子,及Expresso的幫忙,相信各位大大對RE有個基本的了解,網路上當然有許多有關於RE的文章,如果各位大大有興趣http://www.codeproject.com 還有許多關於RE的相關文章。若大大對書有興趣的話,Jeffrey Friedl的Mastering Regular Expressions很多大大都有推(小弟還沒拜讀)。希望籍由這樣的心得報告,能讓對RE有興趣的大大能縮短學習曲線,當然這是小弟第一次接觸RE,若文章中有什麼錯誤或說明的不好的地方,可要請各位大大體諒,並請各位大大將需要修正的地方mail給小弟,小弟會非常感謝各位大大。