30分钟正则表达式指导
很早就看過這篇文章了,很精典。
?
你是否曾經想過正則表達式是什么,怎樣能夠快速得到對它的一個基本的認識?我的目的就是在30分鐘內帶你入門并且對正則表達式有一個基本的理解。事實是正則表達式并沒有它看起來那么復雜。學習它最好的辦法就是開始寫正則表達式并且不斷實踐。在最初的30分鐘之后,你就應該知道一些基本的結構并且有能力在你的程序或者web頁面中設計和使用正則表達式了。對那些想要深入研究的人,現在已經有很多非常好的可用資源來讓你更深入的學習。
?
到底什么是正則表達式?
?
我相信你對模式匹配的“計算機通配符”字符應該比較熟悉了。例如,如果你想要在一個Windows文件夾中找到所有Mircosoft?Word文件,你要搜索“*.doc”,因為你知道星號會被解釋為一個通配符,它匹配所有序列的字符串。正則表達式就是這種功能的一個更加細節的擴展。
在寫處理文本的程序或者web頁面時,定位匹配復雜模式的字符串是很常見的。正則表達式就是用來描述這類模式的。這樣,一個正則表達式就是一個模式的縮減代碼。例如,模式“\w+”是表達“匹配任何包含字母數字字符的非空字符串”的精確方法。.NET框架提供了一個功能強大類庫,它使得在你的應用程序中包含正則表達式更加容易。使用這個庫,你可以輕易地搜索和替換文本,解碼復雜的標題,解析語言,或者驗證文本。
學習正則表達式的神秘的語法的一個好辦法是用例子作為開始學習的對象,然后實踐創建自己的正則表達式。
讓我們開始吧!
一些簡單的例子
?
搜索Elvis
假設你要花費你所有的空余時間來掃描文檔來尋找Elvis仍然活著的證據。你可以使用下面的正則表達式來搜索:
1.?elvis?--?Find?elvis?
這是搜索精確字符序列的一個完全合法的正則表達式。在.NET中,你可以輕松的設置選項來忽略字符的各種情況,所以這個表達式將會匹配“Elivs”,“ELVIS”,或者“eLvIs”。不幸的是,它也將匹配單詞“pelvis”的后五個字母。我們可以改進這個表達式如下:
2.?\belvis\b?--?Find?elvis?as?a?whole?word
現在事情變得更加有趣了。“\b”是一個特殊代碼,它表示“匹配任何單詞的開頭或結尾的位置”。這個表達式將只匹配完整的拼寫為“elvis”的單詞,無論是小寫的還是大寫的情況。
假設你想要找到所有這樣的行,在其中單詞“elvis”后面都跟著單詞“alive”。句點或者點“.”是一個特殊代碼匹配除了換行符之外的任何字符。星號“*”表示重復前面的部分有必要的次數以保證能夠有一個匹配。這樣,“.*”表示“匹配除了換行符之外的任意數目的字符”。現在建立一個表示“搜索在同一行內后面跟著單詞‘alive’的單詞‘elvis’”的表達式就是一件簡單的事了。
3.?\belvis\b.*\balive\b?--?Find?text?with?"elvis"?followed?by?"alive"
僅僅使用幾個特殊字符我們就開始創建功能強大的正則表達式了,而且它們已經開始變得難以被我們人類理解了。
讓我們看看另一個例子。
確定電話號碼的合法性
假設你的web頁面收集顧客的7位電話號碼,而且你希望驗證輸入的電話號碼是正確的格式,“xxx-xxxx”,這里每個“x”是一個數字。下面的表達式將搜索整個文本尋找這樣的一個字符串:
4.?\b\d\d\d-\d\d\d\d?--?Find?seven-digit?phone?number
每個“\d”表示“匹配任何單個數字”。“-”沒有特殊的意義并且按照字面解釋,匹配一個連字符。要避免繁瑣的重復,我們可以使用一個含有相同含義的速記符:
5.?\b\d{3}-\d{4}?--?Find?seven-digit?phone?number?a?better?way
“\d”后面的“{3}”表示“重復前面的字符三次”。
?
.NET正則表達式的基礎
?
讓我們探索一下.NET中正則表達式的基礎
特殊字符
你應該知道幾個有特殊意義的字符。你已經見過了“\b”,“.”,“*”,和“\d”。要匹配任何空白字符,像空格,制表符和換行符,使用“\s”。相似地,“\w”匹配任何字母數字字符。
讓我們嘗試更多的例子:
6.?\ba\w*\b?--?Find?words?that?start?with?the?letter?a
這個搜索一個單詞的開頭(\b),然后是一個字母“a”,接著是任意次數重復的字母數字字符(\w*),最后是一個單詞的結尾(\b)。
7.?\d+?--?Find?repeated?strings?of?digits
這里,“+”與“*”是相似的,除了它需要至少一次重復。
8.?\b\w{6}\b?--?Find?six?letter?words
在Expresso中測試這幾個表達式,然后實踐創建你自己的表達式。下面是一個說明有特殊含義的字符的表格:
| .? | 匹配除換行符外的任何字符 |
| \w | ?匹配任何字母數字字符 |
| \s | 匹配任何空白字符 |
| \d | 匹配任何數字 |
| \b | 匹配一個單詞的開始或結尾 |
| ^ | 匹配字符串的開始 |
| $ | 匹配字字符串的結尾 |
????????表1?正則表達式的常用特殊字符
開始階段
特殊字符“^”和“$”被用來搜索那些必須以一些文本開頭和(或)以一些文本結尾的文本。特別是在驗證輸入時特別有用,在這些驗證中,輸入的整個文本必須要匹配一個模式。例如,要驗證一個7位電話號碼,你可能要用:
9.?^\d{3}-\d{4}$?--?Validate?a?seven-digit?phone?number
這是和第5個例子一樣的,但是強迫它符合整個文本字符串,匹配文本的頭尾之外沒有其他字符。通過在.NET中設置“Multiline”選項,“^”和“$”改變他們的意義為匹配一行文本的起點和結束,而不是整個正文字符串。Expresso的例子使用這個選項。
換碼字符
當你想要匹配這些特殊字符中的一個時會產生一個錯誤,像“^”或者“$”。使用反斜線符號來去掉它們的特殊意義。這樣,“\^”,“\.”,和“\\”,分別匹配文本字符“^”,“.”,和“\”。
重復
你已經見過了“{3}”和“*”可以指定一個單獨字符的重復次數。稍后,你會看到相同的語法怎樣用來重復整個子表達式。此外還有其他幾種方法來指定一個重復,如下表所示:
| * | 重復任意次數 |
| + | 重復一次或多次 |
| ? | 重復一次或多次 |
| {n} | 重復n次 |
| {n,m} | 重復最少n次,最多m次 |
| {n,} | 重復最少n次 |
????????????????????????表2?常用量詞
讓我們試試幾個例子:
10.?\b\w{5,6}\b?--?Find?all?five?and?six?letter?words
11.?\b\d{3}\s\d{3}-\d{4}?--?Find?ten?digit?phone?numbers
12.?\d{3}-\d{2}-\d{4}?--?Social?security?number
13.?^\w*?--?The?first?word?in?the?line?or?in?the?text
在設置和不設置“Multiline”選項的時試試最后一個例子,它改變了“^”的含義。
字符集合
搜索字母數字字符,數字,和空白字符是容易的,但如果你需要搜索一個字符集合中的任意字符時怎么辦?這可以通過在方括號中列出想要的字符來輕松的解決。這樣,“[aeiou]”就能匹配任意韻母,而“[.?!]”就匹配句子末尾的標點。在這個例子中,注意“.”和“?”在方括號中都失去了他們的特殊意義而被解釋為文本含義。我們也可以指定一個范圍的字符,所以“[a-z0-9]”表示“匹配任何小寫字母或者任何數字”。
讓我們試試一個搜索電話號碼的更加復雜的表達式:
14.?\(?\d{3}[)?]\s?\d{3}[-?]\d{4}?A?ten?digit?phone?number
這個表達式將會搜索幾種格式的電話號碼,像“(800)325-3535”或者“650?555?1212”。“\(?”搜索0個或1個左圓括號,“[)]”搜索一個右圓括號或者一個空格。“\s?”搜索0個或一個空白字符。不幸的是,它也會找到像“650)555-1212”這樣括號沒有去掉的情況。在下面,你會看到怎樣用可選項解決這個問題。
否定
有些時候我們需要搜索一個字符,它不是一個很容易定義的字符集合的成員。下面的表格說明了這種字符怎樣指定:
| \W | 匹配任何非字母數字字符 |
| \S | 匹配任何非空白字符 |
| \D | 匹配任何非數字字符 |
| \B | 匹配非單詞開始或結束的位置 |
| [^x] | 匹配任何非x字符 |
| [^aeiou] | 匹配任何不在aeiou中的字符 |
????????????表3?怎樣指定你不想要東西
15.?\S+?--?All?strings?that?do?not?contain?whitespace?characters
后面,我們會看到怎樣使用“lookahead”和“lookbehind”來搜索缺少更加復雜的模式的情況。
可選項
要從幾個可選項中選擇,允許符合任何一個的匹配,使用豎杠“|”來分隔可選項。例如,郵政編碼有兩種,一個是5位的,另一個是9位的加一個連字符。我們可以使用下面的表達式找到任何一種:
16.?\b\d{5}-\d{4}\b|\b\d{5}\b?--?Five?and?nine?digit?Zip?Codes
當使用可選項時,順序是很重要的因為匹配算法將試圖先匹配最左面的選擇。如果這個例子中的順序顛倒過來,表達式將只能找到5位的郵政編碼,而不會找到9位的。我們可以使用可選項來改進十位電話號碼的表達式,允許包含區碼無論是通過空白字符還是連字符劃分的:
17.?(\(\d{3}\)|\d{3})\s?\d{3}[-?]\d{4}?--?Ten?digit?phone?numbers,?a?better?way
?
分組
?
圓括號可以用來劃分一個子表達式來允許重復或者其他特殊的處理,例如:
18.?(\d{1,3}\.){3}\d{1,3}?--?A?simple?IP?address?finder
表達式的第一部分搜索后面跟著一個“\.”的一個一位到三位的數字。這被放在圓括號中并且通過使用修飾符“{3}”被重復三次,后面跟著與之前一樣的表達式而不帶后綴部分。
不幸的是,這個例子允許IP地址中被分隔的部分是任意的一位,兩位,或三位數字,盡管一個合法的IP地址不能有大于255的數字。要是能夠算術比較一個獲取的數字N使N<256就好了,但是只用正則表達式是不能夠辦到的。下一個例子使用模式匹配測試了基于第一位數字的多種可選項來保證限制數字的取值范圍。這表明一個表達式會變得很笨重,盡管搜索模式的描述是簡單的。
19.?((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)?--?IP?finder
一個“回引”用來搜索前面被一個分組捕獲的已匹配文本的再現。例如,“\1”表示“匹配分組1中已捕獲到的文本”。下面是一個例子:
20.?\b(\w+)\b\s*\1\b?--?Find?repeated?words
它的運行過程是先捕獲一個分組1中“(\w+)”表示的至少包含一個字母數字字符的字符串,但僅當它是一個單詞的開始或結束字符時才行。然后它搜索任意數量的空白字符“\s*”后跟以被捕獲的文本“\1”結尾的單詞。
在上面的例子中,想要替換分組“(\w+)”這種寫法,我們可以把它寫成“(?<Word>\w+)”來給這個分組命名為“Word”。一個對這個分組的回引可以寫成“\k<Word>”。試試下面的例子:
21.?\b(?<Word>\w+)\b\s*\k<Word>\b?--?Capture?repeated?word?in?a?named?group
通過使用圓括號,有很多可用的特殊用途的語法元素。一些最常用的歸納如下面這張表格:
| 捕獲 | |
| (exp) | 匹配exp并且在一個自動計數的分組中捕獲它 |
| (?<name>exp) | 匹配exp并且在一個命名的分組中捕獲它 |
| (?:exp) | 匹配exp并且不捕獲它 |
| 察看 | |
| (?=exp) | 匹配任何后綴exp之前的位置 |
| (?<=exp) | 匹配任何前綴exp之后的位置 |
| (?!exp) | 匹配任何未找到的后綴exp之后的位置 |
| (?<!exp) | 匹配任何未找到的前綴exp之前的位置 |
| 評論 | |
| (?#comment) | 評論 |
????????????????????????????????????????????????表4?常用分組結構
前兩個我們已經說過了。第三個“(?:exp)”不會改變匹配行為,它只是不像前兩個那樣捕獲已命名的或者計數的分組。
確定察看(Positive?Lookaround)
下面四個是所謂的前向或后向斷言。它們從當前的匹配向前或向后尋找需要的東西而不在匹配中包含它們。這些表達式匹配一個類似于“^”或“\b”的位置而不匹配任何文本,理解這個是很重要的。由于這個原因,他們也被稱為“零寬度斷言”。最好用例子來解釋它們:
“(?=exp)”是“零寬度確定前向斷言”。它匹配一個文本中在給定后綴之前的位置,但不在匹配中包含這個后綴:
22.?\b\w+(?=ing\b)?--?The?beginning?of?words?ending?with?"ing"
“(?<=exp)”是“零寬度確定后向斷言”。它匹配在給定前綴后面的位置,但不在匹配中包含這個前綴:
23.?(?<=\bre)\w+\b?--?The?end?of?words?starting?with?"re"
下面這個例子可以用來重復向三位數為一組的數字中插入逗號的例子:
24.?(?<=\d)\d{3}\b?--?Three?digits?at?the?end?of?a?word,?preceded?by?a?digit
下面是一個同時搜索前綴和后綴的例子:
25.?(?<=\s)\w+(?=\s)?--?Alphanumeric?strings?bounded?by?whitespace
否定察看(Negative?Lookaround)
之前,我說明了怎樣搜索一個不是特定字符或一個字符集合的成員的字符。那么如果我們想要簡單的驗證一個字符沒有出現,但是不想匹配任何東西怎么辦?例如,如果我們想要搜索其中“q”不是后跟著“u”的單詞怎么辦?我們可以嘗試:
26.?\b\w*q[^u]\w*\b?--?Words?with?"q"?followed?by?NOT?"u"
運行例子你就會看到如果“q”是一個單詞的最后一個字母就不會匹配,比如“Iraq”。這是因為“[^q]”總是匹配一個字符。如果“q”是單詞的最后一個字符,它會匹配后面跟著的空白字符,所以這個例子中表達式結束時匹配兩個完整的單詞。否定察看可以解決這個問題,因為它匹配一個位置而不消耗任何文本。與確定察看一樣,它也可以用來匹配一個任意復雜的子表達式的位置,而不僅僅是一個字符。我們現在可以做得更好:
27.?\b\w*q(?!u)\w*\b?--?Search?for?words?with?"q"?not?followed?by?"u"
我們使用“零寬度否定前向斷言”,“(?!exp)”,只有當后綴“exp”沒有出現時它才成功。下面是另一個例子:
28.?\d{3}(?!\d)?--?Three?digits?not?followed?by?another?digit
相似地,我們可以使用“(?<!exp)”,“零寬度否定后向斷言”,來搜索文本中的一個位置,這里前綴“exp”沒有出現:
29.?(?<![a-z?])\w{7}?--?Strings?of?7?alphanumerics?not?preceded?by?a?letter?or?space
這里是另一個使用后向的例子:
30.?(?<=<(\w+)>).*(?=<\/\1>)?--?Text?between?HTML?tags
這個使用后向搜索一個HTML標記,而使用前向搜索對應的結束標記,這樣,就能獲得中間的文本而不包括兩個標記。
評論
標點的另一個用法是使用“(?#comment)”語法包含評論。一個更好的辦法是設置“Ignore?Pattern?Whitespace”選項,它允許空白字符插入表達式然后當使用表達式時忽略它。設置了這個選項之后,任何文本每行末尾在數字符號“#”后面的東西都被忽略。例如,我們可以格式化先前的例子如下:
31.?Text?between?HTML?tags,?with?comments
(?<=????#?Search?for?a?prefix,?but?exclude?it
??<(\w+)>?#?Match?a?tag?of?alphanumerics?within?angle?brackets
)??????????#?End?the?prefix
.*????????#?Match?any?text
(?=?????#?Search?for?a?suffix,?but?exclude?it
??<\/\1>??#?Match?the?previously?captured?tag?preceded?by?"/"
)?????????#?End?the?suffix
?
?
貪婪與懶惰
?
當一個正則表達式有一個可以接受一個重復次數范圍的量詞(像“.*”),正常的行為是匹配盡可能多的字符。考慮下面的正則表達式:
32.?a.*b?--?The?longest?string?starting?with?a?and?ending?with?b
如果這被用來搜索字符串“aabab”,它會匹配整個字符串“aabab”。這被稱為“貪婪”匹配。有些時候,我們更喜歡“懶惰”匹配,其中一個匹配使用發現的最小數目的重復。表2中所有的量詞可以增加一個問號“?”來轉換到“懶惰”量詞。這樣,“*?”的意思就是“匹配任何數目的匹配,但是使用達到一個成功匹配的最小數目的重復”。現在讓我們試試懶惰版本的例子(32):
33.?a.*?b?--?The?shortest?string?starting?with?a?and?ending?with?b
如果我們把這個應用到相同的字符串“aabab”,它會先匹配“aab”然后匹配“ab”。
| *? | 重復任意次數,但盡可能少 |
| +?? | 匹配一次或多次,但盡可能少 |
| ?? | 重復零次或多次,但盡可能少 |
| {n,m}? | 重復最少n次,但不多于m次,但盡可能少 |
| {n,}? | 重復最少n次,但盡可能少 |
????????????????????????????????????????表5?懶惰量詞
我們遺漏了什么?
我已經描述了很多元素,使用它們來開始創建正則表達式;但是我還遺漏了一些東西,它們在下面的表中歸納出來。這些中的很多都在項目文件中使用額外的例子說明了。例子編號在這個表的左列中列出。
| ? | \a | 報警字符 |
| ? | \b | 通常是單詞邊界,但是在一個字符集合中它表示退格鍵 |
| ? | \t? | 制表符 |
| 34 | \r | 回車 |
| ? | \v | 垂直制表符 |
| ? | \f | 分頁符 |
| 35 | \n | 換行符 |
| ? | \e | ESC |
| 36 | \nnn | ASCII碼八進制數為nnn的字符 |
| 37 | \xnn | 十六進制數為nn的字符 |
| 38 | \unnnn?? | Unicode碼為nnnn的字符 |
| 39 | \cN?? | Control?N字符,例如回車(Ctrl-M)就是\cM |
| 40 | \A | 字符串的開始(像^但是不依賴于多行選項) |
| 41 | \Z | 字符串的結尾或者\n之前的字符串結尾(忽略多行) |
| ? | \z? | 字符串結尾(忽略多行) |
| 42 | \G | 當前搜索的開始階段 |
| 43 | \p{name} | 命名為name的Unicode類中的任何字符,例如\p{IsGreek}? |
| ? | (?>exp) | 貪婪子表達式,也被稱為非回溯子表達式。它只匹配一次然后就不再參與回溯。 |
| 44 | (?<x>-<y>exp)?or?(?-<y>exp) | Balancing?group.?This?is?complicated?but?powerful.?It?allows?named?capture?groups?to?be?manipulated?on?a?push?down/pop?up?stack?and?can?be?used,?for?example,?to?search?for?matching?parentheses,?which?is?otherwise?not?possible?with?regular?expressions.?See?the?example?in?the?project?file.? |
| 45 | (?im-nsx:exp) | 正則表達式選項為子表達式exp? |
| 46 | (?im-nsx) | Change?the?regular?expression?options?for?the?rest?of?the?enclosing?group? |
| ? | (?(exp)yes|no) | The?subexpression?exp?is?treated?as?a?zero-width?positive?lookahead.?If?it?matches?at?this?point,?the?subexpression?yes?becomes?the?next?match,?otherwise?no?is?used.? |
| ? | (?(exp)yes) | Same?as?above?but?with?an?empty?no?expression? |
| ? | (?(name)yes|no) | This?is?the?same?syntax?as?the?preceding?case.?If?name?is?a?valid?group?name,?the?yes?expression?is?matched?if?the?named?group?had?a?successful?match,?otherwise?the?no?expression?is?matched.? |
| 47? | (?(name)yes) | Same?as?above?but?with?an?empty?no?expression? |
表6?我們遺漏的東西。左端的列顯示了項目文件中說明這個結構的例子的序號
結論
我們已經給出了很多例子來說明.NET正則表達式的關鍵特性,強調使用工具(如Expresso)來測試,實踐,然后是用例子來學習。如果你想要深入的研究,網上也有很多在線資源會幫助你更深入的學習。你可以從訪問Ultrapico網站開始。如果你想讀一本相關書籍,我建議Jeffrey?Friedl寫的最新版的《Mastering?Regular?Expressions》。
Code?Project中還有很多不錯的文章,其中包含下面的教程:
·An?Introduction?to?Regular?Expressions?by?Uwe?Keim?
·Microsoft?Visual?C#?.NET?Developer's?Cookbook:?Chapter?on?Strings?and?Regular?Expressions
?
總結
以上是生活随笔為你收集整理的30分钟正则表达式指导的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 做 局域网聊天 的人越来越多了
- 下一篇: 平时喜欢使用的软件总结 欲善其事,必先利