今天下午“光榮”的分到了59個bug,磨刀不誤砍柴工,先學一下正則表達式……
入門
\b 是正則表達式規定的一個特殊代碼(好吧,某些人叫它 元字符,metacharacter ),代表著 單詞的開頭或結尾,也就是單詞的分界處 。雖然通常英文的單詞是由空格,標點符號或者換行來分隔的,但是 \b 并不匹配這些單詞分隔字符中的任何一個,它 只匹配一個位置 。
如果需要更精確的說法, \b 匹配這樣的位置:它的前一個字符和后一個字符不全是(一個是,一個不是或不存在) \w 。
假如你要找的是 hi后面不遠處跟著一個Lucy ,你應該用 \bhi\b.*\bLucy\b 。若要匹配"A B",則使用 A\sB 或者 A\b\s\bB 等。 表達式 " . \b . " 在匹配 "@@@abc" 時 ,匹配結果是:成功;匹配到的內容是:"@a";匹配到的位置是:開始于2,結束于4。 表達式 " \b end \b " 在匹配 "weekend,endfor,end" 時 ,匹配結果是:成功;匹配到的內容是:"end";匹配到的位置是:開始于15,結束于18。
這里, . 是另一個元字符,匹配 除了換行符以外的任意字符 。 * 同樣是元字符,不過它代表的不是字符,也不是位置,而是數量——它指定* 前邊的內容可以連續重復使用任意次以使整個表達式得到匹配 。因此, .* 連在一起就意味著 任意數量的不包含換行的字符 。?匹配 重復零次或一次 。
元字符
\s
匹配
任意的空白符,包括空格,制表符(Tab),換行符,中文全角空格等
。
\w
匹配
字母或數字或下劃線或漢字等
。
對中文/漢字的特殊處理是由.Net提供的正則表達式引擎支持的,其它環境下的具體情況請查看相關文檔。
和忽略大小寫的選項類似,有些正則表達式處理工具還有一個處理多行的選項。如果選中了這個選項, ^ 和 $ 的意義就變成了 匹配行的開始處和結束處 。
分枝條件
\(?0\d{2}[) -]?\d{8} 。
“(”和“)”也是元字符,后面的分組節里會提到,所以在這里需要使用轉義。
這個表達式可以匹配 幾種格式的電話號碼 ,像 (010)88886666 ,或 022-22334455 ,或 02912345678 等。
不幸的是,剛才那個表達式也能匹配 010)12345678 或 (022-87654321 這樣的“不正確”的格式。要解決這個問題,我們需要用到 分枝條件 。正則表達式里的 分枝條件 指的是有幾種規則,如果滿足其中任意一種規則都應該當成匹配,具體方法是用 | 把不同的規則分隔開。聽不明白?沒關系,看例子:
0\d{2}-\d{8}|0\d{3}-\d{7} 這個表達式能 匹配兩種以連字號分隔的電話號碼:一種是三位區號,8位本地號(如010-12345678),一種是4位區號,7位本地號(0376-2233445) 。
\(?0\d{2}\)?[- ]?\d{8}|0\d{2}[- ]?\d{8} 這個表達式 匹配3位區號的電話號碼,其中區號可以用小括號括起來,也可以不用,區號與本地號間可以用連字號或空格間隔,也可以沒有間隔 。你可以試試用分枝條件把這個表達式擴展成也支持4位區號的。
\d{5}-\d{4}|\d{5} 這個表達式用于匹配美國的郵政編碼。美國郵編的規則是5位數字,或者用連字號間隔的9位數字。之所以要給出這個例子是因為它能說明一個問題: 使用分枝條件時,要注意各個條件的順序 。如果你把它改成 \d{5}|\d{5}-\d{4} 的話,那么就只會匹配5位的郵編(以及9位郵編的前5位)。原因是匹配分枝條件時,將會從左到右地測試每個條件,如果滿足了某個分枝的話,就不會去再管其它的條件了。
反義
代碼/語法 說明
\W | 匹配任意不是字母,數字,下劃線,漢字的字符 |
\S | 匹配任意不是空白符的字符 |
\D | 匹配任意非數字的字符 |
\B | 匹配不是單詞開頭或結束的位置 |
[^x] | 匹配除了x以外的任意字符 |
[^aeiou] | 匹配除了aeiou這幾個字母以外的任意字符 |
例:<a[^>]+> 匹配 用尖括號括起來的以a開頭的字符串 。
分組與后向引用
可以用小括號來指定 子表達式 (也叫做 分組 ),然后你就可以指定這個子表達式的重復次數了。例如: 描述一個正確的IP地址: ((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?) 。
使用小括號指定一個子表達式后,匹配這個子表達式的文本(也就是此分組捕獲的內容)可以在表達式或其它程序中作進一步的處理。默認情況下,每個分組會自動擁有一個 組號 , 分組0對應整個正則表達式。 后向引用 用于重復搜索前面某個分組匹配的文本。例如, \1 代表 分組1匹配的文本 。 \b(\w+)\b\s+\1\b 可以用來匹配 重復的單詞 ,像 go go , 或者 kitty kitty 。
實際上組號分配過程是要從左向右掃描兩遍的:第一遍只給未命名組分配,第二遍只給命名組分配--因此所有命名組的組號都大于未命名的組號, 你可以使用 (?:exp) 這樣的語法來剝奪一個分組對組號分配的參與權。
你也可以自己指定子表達式的 組名 。 (?<Word>\w+) (或者把尖括號換成 ' 也行: (?'Word'\w+) ),這樣就把 \w+ 的組名指定為 Word 了。要反向引用這個分組 捕獲 的內容,你可以使用 \k<Word> ,所以上一個例子也可以寫成這樣: \b(?<Word>\w+)\b\s+\k<Word>\b 。
使用小括號的時候,還有很多特定用途的語法。下面列出了最常用的一些:
分類 代碼/語法 說明 捕獲 零寬斷言 注釋
(exp) | 匹配exp,并捕獲文本到自動命名的組里 |
(?<name>exp) | 匹配exp,并捕獲文本到名稱為name的組里,也可以寫成(?'name'exp) |
(?:exp) | 匹配exp,不捕獲匹配的文本,也不給此分組分配組號 |
(?=exp) | 匹配exp前面的位置 |
(?<=exp) | 匹配exp后面的位置 |
(?!exp) | 匹配后面跟的不是exp的位置 |
(?<!exp) | 匹配前面不是exp的位置 |
(?#comment) |
這種類型的分組不對正則表達式的處理產生任何影響,用于提供注釋讓人閱讀
|
零寬斷言
斷言用來聲明一個應該為真的事實。正則表達式中只有當斷言為真時才會繼續進行匹配。 接下來的四個用于查找在某些內容(但并不包括這些內容)之前或之后的東西,也就是說它們像 \b , ^ , $ 那樣用于指定一個位置,這個位置應該滿足一定的條件(即斷言),因此它們也被稱為 零寬斷言 。
(?=exp) 也叫 零寬度正預測先行斷言 ,它 斷言自身出現的位置的后面能匹配表達式exp 。比如 \b\w+(?=ing\b) ,匹配 以ing結尾的單詞的前面部分(除了ing以外的部分) ,如查找 I'm singing while you're dancing. 時,它會匹配 sing 和 danc 。 \b(?=\w{7}\b)\w*clip\w*\b ,匹配包含clip的七個字符的單詞。
(?<=exp) 也叫 零寬度正回顧后發斷言 ,它 斷言自身出現的位置的前面能匹配表達式exp 。比如 (?<=\bre)\w+\b 會匹配 以re開頭的單詞的后半部分(除了re以外的部分) ,例如在查找 reading a book 時,它匹配 ading 。
假如你想要給一個很長的數字中每三位間加一個逗號(當然是從右邊加起了),你可以這樣查找需要在前面和里面添加逗號的部分: ((?<=\d)\d{3})+\b ,用它對 1234567890 進行查找時結果是 234567890 。
下面這個例子同時使用了這兩種斷言: (?<=\s)\d+(?=\s) 匹配 以空白符間隔的數字(再次強調,不包括這些空白符) 。
負向零寬斷言
前面我們提到過怎么查找 不是某個字符或不在某個字符類里 的字符的方法(反義)。但是如果我們只是想要 確保某個字符沒有出現,但并不想去匹配它 時怎么辦?例如,如果我們想查找這樣的單詞--它里面出現了字母q,但是q后面跟的不是字母u,我們可以嘗試這樣:
\b\w*q[^u]\w*\b 匹配 包含 后面不是字母u的字母q 的單詞 。但是如果多做測試(或者你思維足夠敏銳,直接就觀察出來了),你會發現,如果q出現在單詞的結尾的話,像 Iraq , Benq ,這個表達式就會出錯。這是因為 [^u] 總要匹配一個字符,所以如果q是單詞的最后一個字符的話,后面的 [^u] 將會匹配q后面的單詞分隔符(可能是空格,或者是句號或其它的什么),后面的 \w*\b 將會匹配下一個單詞,于是 \b\w*q[^u]\w*\b 就能匹配整個 Iraq fighting 。 負向零寬斷言 能解決這樣的問題,因為它只匹配一個位置,并不 消費 任何字符。現在,我們可以這樣來解決這個問題: \b\w*q(?!u)\w*\b 。
零寬度負預測先行斷言 (?!exp) , 斷言此位置的后面不能匹配表達式exp 。例如: \d{3}(?!\d) 匹配 三位數字,而且這三位數字的后面不能是數字 ; \b((?!abc)\w)+\b 匹配 不包含連續字符串abc的單詞 。
同理,我們可以用 (?<!exp) , 零寬度負回顧后發斷言 來 斷言此位置的前面不能匹配表達式exp : (?<![a-z])\d{7} 匹配 前面不是小寫字母的七位數字 。
請詳細分析表達式 (?<=<(\w+)>).*(?=<\/\1>) ,這個表達式最能表現零寬斷言的真正用途。
一個更復雜的例子: (?<=<(\w+)>).*(?=<\/\1>) 匹配 不包含屬性的簡單HTML標簽內里的內容 。 (?<=<(\w+)>) 指定了這樣的 前綴 : 被尖括號括起來的單詞 (比如可能是<b>),然后是 .* (任意的字符串),最后是一個 后綴 (?=<\/\1>) 。注意后綴里的 \/ ,它用到了前面提過的字符轉義; \1 則是一個反向引用,引用的正是 捕獲的第一組 ,前面的 (\w+) 匹配的內容,這樣如果前綴實際上是<b>的話,后綴就是</b>了。整個表達式匹配的是<b>和</b>之間的內容(再次提醒,不包括前綴和后綴本身)。
貪婪與懶惰
當正則表達式中包含能接受重復的限定符時,通常的行為是(在使整個表達式能得到匹配的前提下)匹配 盡可能多 的字符。以這個表達式為例: a.*b ,它將會匹配 最長的以a開始,以b結束的字符串 。如果用它來搜索 aabab 的話,它會匹配整個字符串 aabab 。這被稱為 貪婪 匹配。
有時,我們更需要 懶惰 匹配,也就是匹配 盡可能少 的字符。前面給出的限定符都可以被轉化為懶惰匹配模式,只要在它后面加上一個問號 ? 。這樣 .*? 就意味著 匹配任意數量的重復,但是在能使整個匹配成功的前提下使用最少的重復 。現在看看懶惰版的例子吧:
a.*?b 匹配 最短的,以a開始,以b結束的字符串 。如果把它應用于 aabab 的話,它會匹配 aab(第一到第三個字符) 和 ab(第四到第五個字符) 。
為什么第一個匹配是aab(第一到第三個字符)而不是ab(第二到第三個字符)?簡單地說,因為正則表達式有另一條規則,比懶惰/貪婪規則的優先級更高:最先開始的匹配擁有最高的優先權——The match that begins earliest wins。
代碼/語法 說明
*? | 重復任意次,但盡可能少重復 |
+? | 重復1次或更多次,但盡可能少重復 |
?? | 重復0次或1次,但盡可能少重復 |
{n,m}? | 重復n到m次,但盡可能少重復 |
{n,}? |
重復n次以上,但盡可能少重復
|
處理選項
在C#中,你可以使用Regex(String, RegexOptions)構造函數來設置正則表達式的處理選項。如:Regex regex = new Regex(@"\ba\w{6}\b", RegexOptions.IgnoreCase);
上面介紹了幾個選項如忽略大小寫,處理多行等,這些選項能用來改變處理正則表達式的方式。下面是.Net中常用的正則表達式選項:
名稱 說明
IgnoreCase(忽略大小寫) | 匹配時不區分大小寫。 |
Multiline(多行模式) |
更改 ^ 和 $ 的含義,使它們分別在任意一行的行首和行尾匹配,而不僅僅在整個字符串的開頭和結尾匹配。(在此模式下, $ 的精確含意是:匹配\n之前的位置以及字符串結束前的位置.)
例如:默認情況下,表達式 "^" 和 "$" 只匹配字符串的開始 ① 和結尾 ④ 位置。如:
|
Singleline(單行模式) | 默認情況下,小數點 "." 匹配除了換行符(\n)以外的字符。配置為 Singleline 可使小數點可匹配包括換行符在內的所有字符。 |
IgnorePatternWhitespace(忽略空白) | 忽略表達式中的非轉義空白并啟用由 # 標記的注釋。 |
ExplicitCapture(顯式捕獲) | 僅捕獲已被顯式命名的組。 |
一個經常被問到的問題是:是不是只能同時使用多行模式和單行模式中的一種?答案是:不是。 這兩個選項之間沒有任何關系 ,除了它們的名字比較相似(以至于讓人感到疑惑)以外。
其它語法
\a | 報警字符(打印它的效果是電腦嘀一聲) |
\b | 通常是單詞分界位置,但如果在字符類里使用代表退格 |
\t | 制表符,Tab |
\r | 回車 |
\v | 豎向制表符 |
\f | 換頁符 |
\n | 換行符 |
\e | Escape |
\0nn | ASCII代碼中八進制代碼為nn的字符 |
\xnn | ASCII代碼中十六進制代碼為nn的字符 |
\unnnn | Unicode代碼中十六進制代碼為nnnn的字符 |
\cN | ASCII控制字符。比如\cC代表Ctrl+C |
\A | 字符串開頭(類似^,但不受處理多行選項的影響) |
\Z | 字符串結尾或行尾(不受處理多行選項的影響) |
\z | 字符串結尾(類似$,但不受處理多行選項的影響) |
\G | 當前搜索的開頭 |
\p{name} | Unicode中命名為name的字符類,例如\p{IsGreek} |
(?>exp) | 貪婪子表達式 |
(?<x>-<y>exp) | 平衡組 |
(?im-nsx:exp) | 在子表達式exp中改變處理選項 |
(?im-nsx) | 為表達式后面的部分改變處理選項 |
(?(exp)yes|no) | 把exp當作零寬正向先行斷言,如果在這個位置能匹配,使用yes作為此組的表達式;否則使用no |
(?(exp)yes) | 同上,只是使用空表達式作為no |
(?(name)yes|no) | 如果命名為name的組捕獲到了內容,使用yes作為表達式;否則使用no |
(?(name)yes) | 同上,只是使用空表達式作為no |
實戰--牛刀小試


.alert(
'提示',
'請在下拉列表中選擇一條消息!');

New: 字符串替換實例(Java)
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
