You are on page 1of 18

正則表達式30分鐘⼊⾨教程 29.04.

22, 00:27

⾸⾴ 博客 ⼯具 項⽬ 打賞

正則表達式30分鐘⼊⾨教程
版本:v2.4.1 (2019-11-15) 作者:deerchao 轉載請註明來源

⽬錄

相關鏈接:
1. 本⽂⽬標
2. 如何使⽤本教程 常⽤正則表達式
3. 正則表達式到底是什麼東西? JavaScript 在線正則測試器
4. ⼊⾨ .Net 正則表達式測試⼯具
5. 測試正則表達式 正則表達式引擎特性對⽐
6. 元字符
7. 字符轉義
8. 重複
9. 字符類
10. 分枝條件
11. 反義
12. 分組
13. 後向引⽤
14. 零寬斷⾔
15. 負向零寬斷⾔
16. 註釋
17. 貪婪與懶惰
18. 處理選項
19. 平衡組/遞歸匹配
20. 還有些什麼東西沒提到
21. 聯繫作者
22. 網上的資源及本⽂參考⽂獻
23. 更新紀錄

本⽂⽬標
30分鐘內讓你明⽩正則表達式是什麼,並對它有⼀些基本的瞭解,讓你可以在⾃⼰的程
序或網⾴裡使⽤它。

如何使⽤本教程
file:///Volumes/Zeit/Books/正則表達式30分鐘⼊⾨教程.webarchive Seite 1 von 18
正則表達式30分鐘⼊⾨教程 29.04.22, 00:27

別被下⾯那些複雜的表達式嚇倒,只 最重要的是——請給我30分鐘,如果
要跟著我⼀步⼀步來,你會發現正則表達 你沒有使⽤正則表達式的經驗,請不要試
式其實並沒有想像中的那麼困難。當然, 圖在30秒內⼊⾨——除⾮你是超⼈ :)
如果你看完了這篇教程之後,發現⾃⼰明
⽩了很多,卻又幾乎什麼都記不得,那也
是很正常的——我認為,沒接觸過正則表
達式的⼈在看完這篇教程後,能把提到過
的語法記住80%以上的可能性為零。這裡
只是讓你明⽩基本的原理,以後你還需要
多練習,多使⽤,才能熟練掌握正則表達
式。
除了作為⼊⾨教程之外,本⽂還試圖
成為可以在⽇常⼯作中使⽤的正則表達式
語法參考⼿冊。就作者本⼈的經歷來說,
這個⽬標還是完成得不錯的——你看,我
⾃⼰也沒能把所有的東西記下來,不是
嗎?
清除格式 ⽂本格式約定: 專業術
語 元字符/語法格式 正則表達式 正則表
達式中的⼀部分(⽤於分析) 對其進⾏匹配
的源字符串 對正則表達式或其中⼀部分
的說明
隱藏邊注 本⽂右邊有⼀些註釋,主要
是⽤來提供⼀些相關信息,或者給沒有程
序員背景的讀者解釋⼀些基本概念,通常
可以忽略。
本⽂介紹的⼤部分正則語法,在不同
的正則表達式引擎中都可以使⽤,但也有
⼀些會有所差異。本⽂介紹的是 .Net 下的
正則表達式,其它環境下的具體情況可以
在讀完本⽂後去參考官⽅⽂檔,或者查看
正則表達式引擎特性對⽐。

正則表達式到底是什麼東西?

file:///Volumes/Zeit/Books/正則表達式30分鐘⼊⾨教程.webarchive Seite 2 von 18


正則表達式30分鐘⼊⾨教程 29.04.22, 00:27

在編寫處理字符串的程序或網⾴時, 字符 是計算機軟件處理⽂字時最基本
經常會有查找符合某些複雜規則的字符串 的單位,可能是字母,數字,標點符號,
的需要。 正則表達式 就是⽤於描述這些規 空格,換⾏符,漢字等等。 字符串 是0個
則的⼯具。換句話說,正則表達式就是記 或更多個字符的序列。 ⽂本 也就是⽂字,
錄⽂本規則的代碼。 字符串。說某個字符串 匹配 某個正則表達
很可能你使⽤過Windows/Dos下⽤於 式,通常是指這個字符串裡有⼀部分(或
⽂件查找的 通配符(wildcard) ,也就是 * 幾部分分別)能滿⾜表達式給出的條件。
和 ? 。如果你想查找某個⽬錄下的所有的
Word⽂檔的話,你會搜索 *.doc 。在這
裡, * 會被解釋成任意的字符串。和通配
符類似,正則表達式也是⽤來進⾏⽂本匹
配的⼯具,只不過⽐起通配符,它能更精
確地描述你的需求——當然,代價就是更
復雜——⽐如你可以編寫⼀個正則表達
式,⽤來查找 所有以0開頭,後⾯跟著2-3
個數字,然後是⼀個連字號“-”,最後是7
或8位數字的字符串 (像 010-12345678
或 0376-7654321 )。

⼊⾨
學習正則表達式的最好⽅法是從例⼦開始,理解例⼦之後再⾃⼰對例⼦進⾏修改,實
驗。下⾯給出了不少簡單的例⼦,並對它們作了詳細的說明。
假設你在⼀篇英⽂⼩說裡查找 hi ,你可以使⽤正則表達式 hi 。
這幾乎是最簡單的正則表達式了,它可以精確匹配這樣的字符串: 由兩個字符組成,前
⼀個字符是h,後⼀個是i 。通常,處理正則表達式的⼯具會提供⼀個忽略⼤⼩寫的選項,如果
選中了這個選項,它可以匹配 hi , HI , Hi , hI 這四種情況中的任意⼀種。
不幸的是,很多單詞裡包含 hi 這兩個連續的字符,⽐如 him , history , high 等等。⽤ hi 來
查找的話,這裡邊的 hi 也會被找出來。如果要 精確地查找hi這個單詞 的話,我們應該使
⽤ \bhi\b 。
\b 是正則表達式規定的⼀個特殊代 如果需要更精確的說法, \b 匹配這樣
碼(好吧,某些⼈叫它 元字符, 的位置:它的前⼀個字符和後⼀個字符不
metacharacter ),代表著 單詞的開頭或結 全是(⼀個是,⼀個不是或不存在) \w 。
尾,也就是單詞的分界處 。雖然通常英⽂
的單詞是由空格,標點符號或者換⾏來分
隔的,但是 \b 並不匹配這些單詞分隔字
符中的任何⼀個,它只匹配⼀個位置。

file:///Volumes/Zeit/Books/正則表達式30分鐘⼊⾨教程.webarchive Seite 3 von 18


正則表達式30分鐘⼊⾨教程 29.04.22, 00:27

假如你要找的是 hi後⾯不遠處跟著⼀ 換⾏符就是'\n',ASCII編碼為10(⼗六進


個Lucy ,你應該⽤ \bhi\b.*\bLucy\b 。 制0x0A)的字符。
這裡, . 是另⼀個元字符,匹配 除了
換⾏符以外的任意字符 。 * 同樣是元字
符,不過它代表的不是字符,也不是位
置,⽽是數量——它指定* 前邊的內容可
以連續重複使⽤任意次以使整個表達式得
到匹配 。因此, .* 連在⼀起就意味著 任
意數量的不包含換⾏的字符 。現
在 \bhi\b.*\bLucy\b 的意思就很明顯了:
先是⼀個單詞hi,然後是任意個任意字符
(但不能是換⾏),最後是Lucy這個單詞 。
如果同時使⽤其它元字符,我們就能構造出功能更強⼤的正則表達式。⽐如下⾯這個例
⼦:
0\d\d-\d\d\d\d\d\d\d\d 匹配這樣的字符串: 以0開頭,然後是兩個數字,然後是⼀個
連字號“-”,最後是8個數字 (也就是中國的電話號碼。當然,這個例⼦只能匹配區號為3位的
情形)。
這裡的 \d 是個新的元字符,匹配 ⼀位數字(0,或1,或2,或……) 。 - 不是元字符,只
匹配它本⾝——連字符(或者減號,或者中橫線,或者隨你怎麼稱呼它)。
為了避免那麼多煩⼈的重複,我們也可以這樣寫這個表達式: 0\d{2}-\d{8} 。這裡 \d
後⾯的 {2} ( {8} )的意思是前⾯ \d 必須連續重複匹配2次(8次) 。

測試正則表達式

file:///Volumes/Zeit/Books/正則表達式30分鐘⼊⾨教程.webarchive Seite 4 von 18


正則表達式30分鐘⼊⾨教程 29.04.22, 00:27

如果你不覺得正則表達式很難讀寫的 你也可以試試這個在線測試⼯具:
話,要麼你是⼀個天才,要麼,你不是地 Wegester, JavaScript正則表達式測試器。
球⼈。正則表達式的語法很令⼈頭疼,即
使對經常使⽤它的⼈來說也是如此。由於
難於讀寫,容易出錯,所以找⼀種⼯具對
正則表達式進⾏測試是很有必要的。
不同的環境下正則表達式的⼀些細節
是不相同的,本教程介紹的是微軟 .Net
Framework 4.5 下正則表達式的⾏為,所
以,我向你推薦我編寫的.Net下的⼯具
Regester。請參考該⾴⾯的說明來安裝和
運⾏該軟件。
下⾯是Regester運⾏時的截圖:

元字符

現在你已經知道幾個很有⽤的元字符 對中⽂/漢字的特殊處理是由.Net提供
了,如 \b , . , * ,還有 \d .正則表達式裡還 的正則表達式引擎⽀持的,其它環境下的
有更多的元字符,⽐如 \s 匹配 任意的空 具體情況請查看相關⽂檔。
⽩符,包括空格,製表符(Tab),換⾏符,
中⽂全⾓空格等 。 \w 匹配 字母或數字或
下劃線或漢字等 。

file:///Volumes/Zeit/Books/正則表達式30分鐘⼊⾨教程.webarchive Seite 5 von 18


正則表達式30分鐘⼊⾨教程 29.04.22, 00:27

下⾯來看看更多的例⼦: 好吧,現在我們說說正則表達式裡的
\ba\w*\b 匹配 以字母 a 開頭的單詞 單詞是什麼意思吧:就是不少於⼀個的連
——先是某個單詞開始處( \b ),然後是字 續的 \w 。不錯,這與學習英⽂時要背的成
母 a ,然後是任意數量的字母或數字 千上萬個同名的東西的確關係不⼤ :)
( \w* ),最後是單詞結束處( \b ) 。
\d+ 匹配 1個或更多連續的數字 。這
裡的 + 是和 * 類似的元字符,不同的是 *
匹配 重複任意次(可能是0次) ,⽽ + 則匹
配 重複1次或更多次 。
\b\w{6}\b 匹配 剛好6個字符的單
詞。
表1.常⽤的元字符
代碼 說明
. 匹配除換⾏符以外的任意字符
\w 匹配字母或數字或下劃線或漢字
\s 匹配任意的空⽩符
\d 匹配數字
\b 匹配單詞的開始或結束
^ 匹配字符串的開始
$ 匹配字符串的結束

file:///Volumes/Zeit/Books/正則表達式30分鐘⼊⾨教程.webarchive Seite 6 von 18


正則表達式30分鐘⼊⾨教程 29.04.22, 00:27

元字符 ^ (和數字6在同⼀個鍵位上的 正則表達式引擎通常會提供⼀個“測試


符號)和 $ 都匹配⼀個位置,這和 \b 有點 指定的字符串是否匹配⼀個正則表達式”的
類似。 ^ 匹配你要⽤來查找的字符串的開 ⽅法,如JavaScript裡的RegExp.test()⽅法
頭, $ 匹配結尾。這兩個代碼在驗證輸⼊ 或.NET裡的Regex.IsMatch()⽅法。這裡的
的內容時⾮常有⽤,⽐如⼀個網站如果要 匹配是指是字符串裡有沒有符合表達式規
求你填寫的QQ號必須為5位到12位數字 則的部分。如果不使⽤ ^ 和 $ 的話,對於
時,可以使⽤: ^\d{5,12}$ 。 \d{5,12} ⽽⾔,使⽤這樣的⽅法就只能保
這裡的 {5,12} 和前⾯介紹過的 {2} 是 證字符串裡 包含5到12連續位數字 ,⽽不
類似的,只不過 {2} 匹配 只能不多不少重 是整個字符串就是5到12位數字。
複2次 , {5,12} 則是 重複的次數不能少於5
次,不能多於12次 ,否則都不匹配。
因為使⽤了 ^ 和 $ ,所以輸⼊的整個
字符串都要⽤來和 \d{5,12} 來匹配,也就
是說整個輸⼊ 必須是5到12個數字 ,因此
如果輸⼊的QQ號能匹配這個正則表達式
的話,那就符合要求了。
和忽略⼤⼩寫的選項類似,有些正則
表達式處理⼯具還有⼀個處理多⾏的選
項。如果選中了這個選項, ^ 和 $ 的意義
就變成了 匹配⾏的開始處和結束處 。

字符轉義
如果你想查找元字符本⾝的話,⽐如你查找 . ,或者 * ,就出現了問題:你沒辦法指定它
們,因為它們會被解釋成別的意思。這時你就得使⽤ \ 來取消這些字符的特殊意義。因此,
你應該使⽤ \. 和 \* 。當然,要查找 \ 本⾝,你也得⽤ \\ .
例如: deerchao\.cn 匹配 deerchao.cn , C:\\Windows 匹配 C:\Windows 。

重複
你已經看過了前⾯的 * , + , {2} , {5,12} 這幾個匹配重複的⽅式了。下⾯是正則表達式中所
有的限定符(指定數量的代碼,例如*,{5,12}等):
表2.常⽤的限定符
代碼/語法 說明
* 重複零次或更多次
+ 重複⼀次或更多次
? 重複零次或⼀次
{n} 重複n次

file:///Volumes/Zeit/Books/正則表達式30分鐘⼊⾨教程.webarchive Seite 7 von 18


正則表達式30分鐘⼊⾨教程 29.04.22, 00:27

{n,} 重複n次或更多次
{n,m} 重複n到m次
下⾯是⼀些使⽤重複的例⼦:
Windows\d+ 匹配 Windows後⾯跟1個或更多數字
^\w+ 匹配 ⼀⾏的第⼀個單詞(或整個字符串的第⼀個單詞,具體匹配哪個意思得看選項
設置)

字符類
要想查找數字,字母或數字,空⽩是很簡單的,因為已經有了對應這些字符集合的元字
符,但是如果你想匹配沒有預定義元字符的字符集合(⽐如元⾳字母a,e,i,o,u),應該怎麼辦?
很簡單,你只需要在⽅括號裡列出它們就⾏了,像 [aeiou] 就匹配 任何⼀個英⽂元⾳字
母 , [.?!] 匹配 標點符號(.或?或!) 。
我們也可以輕鬆地指定⼀個字符 範圍 ,像 [0-9] 代表的含意與 \d 就是完全⼀致的: ⼀位
數字 ;同理 [a-z0-9A-Z_] 也完全等同於 \w (如果只考慮英⽂的話)。
下⾯是⼀個更復雜的表達式: \(? “ ( ”和“ ) ”也是元字符,後⾯的分組節
0\d{2}[) -]?\d{8} 。 裡會提到,所以在這裡需要使⽤轉義。
這個表達式可以匹配 幾種格式的電話
號碼 ,像 (010)88886666 ,或 022-
22334455 ,或 02912345678 等。我們對它
進⾏⼀些分析吧:⾸先是⼀個轉義字符 \
( ,它能出現0次或1次( ? ),然後是⼀個 0 ,後
⾯跟著2個數字( \d{2} ),然後是 ) 或 - 或
空格 中的⼀個,它出現1次或不出現( ? ),
最後是8個數字( \d{8} )。

分枝條件
不幸的是,剛才那個表達式也能匹配 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位數字,
file:///Volumes/Zeit/Books/正則表達式30分鐘⼊⾨教程.webarchive Seite 8 von 18
正則表達式30分鐘⼊⾨教程 29.04.22, 00:27

或者⽤連字號間隔的9位數字。之所以要給出這個例⼦是因為它能說明⼀個問題:使⽤分枝
條件時,要注意各個條件的順序。如果你把它改成 \d{5}|\d{5}-\d{4} 的話,那麼就只會匹
配5位的郵編(以及9位郵編的前5位)。原因是匹配分枝條件時,將會從左到右地測試每個條
件,如果滿⾜了某個分枝的話,就不會去再管其它的條件了。

分組
我們已經提到了怎麼重複單個字符(直接在字符後⾯加上限定符就⾏了);但如果想要
重複多個字符又該怎麼辦?你可以⽤⼩括號來指定 ⼦表達式 (也叫做 分組 ),然後你就可以指
定這個⼦表達式的重複次數了,你也可以對⼦表達式進⾏其它⼀些操作(後⾯會有介紹)。
(\d{1,3}\.){3}\d{1,3} 是⼀個 簡單的 IP地址中每個數字都不能⼤於255. 經
IP地址匹配 表達式。要理解這個表達式, 常有⼈問我, 01.02.03.04 這樣前⾯帶有0的數
請按下列順序分析它: \d{1,3} 匹配 1到3 字, 是不是正確的IP地址呢? 答案是: 是的,
位的數字 , (\d{1,3}\.){3} 匹配 三位數字 IP 地址裡的數字可以包含有前導 0 (leading
加上⼀個英⽂句號(這個整體也就是這個 zeroes).
分組 )重複3次 ,最後再加上 ⼀個⼀到三
位的數字 ( \d{1,3} )。
不幸的是,它也將匹
配 256.300.888.999 這種不可能存在的IP地
址。如果能使⽤算術⽐較的話,或許能簡
單地解決這個問題,但是正則表達式中並
不提供關於數學的任何功能,所以只能使
⽤冗⾧的分組,選擇,字符類來描述⼀個
正確的IP地址: ((2[0-4]\d|25[0-5]|[01]?
\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?) 。
理解這個表達式的關鍵是理解 2[0-
4]\d|25[0-5]|[01]?\d\d? ,這裡我就不細說
了,你⾃⼰應該能分析得出來它的意義。

反義
有時需要查找不屬於某個能簡單定義的字符類的字符。⽐如想查找除了數字以外,其它
任意字符都⾏的情況,這時需要⽤到 反義 :
表3.常⽤的反義代碼
代碼/語法 說明
\W 匹配任意不是字母,數字,下劃線,漢字的字符
\S 匹配任意不是空⽩符的字符
\D 匹配任意⾮數字的字符

file:///Volumes/Zeit/Books/正則表達式30分鐘⼊⾨教程.webarchive Seite 9 von 18


正則表達式30分鐘⼊⾨教程 29.04.22, 00:27

\B 匹配不是單詞開頭或結束的位置
[^x] 匹配除了x以外的任意字符
[^aeiou] 匹配除了aeiou這幾個字母以外的任意字符
例⼦: \S+ 匹配 不包含空⽩符的字符串 。
<a[^>]+> 匹配 ⽤尖括號括起來的以a開頭的字符串 。

後向引⽤

使⽤⼩括號指定⼀個⼦表達式後,匹配這 呃……其實,組號分配還不像我剛
個⼦表達式的⽂本(也就是此分組捕獲的內容)可 說得那麼簡單:
以在表達式或其它程序中作進⼀步的處理。默
認情況下,每個分組會⾃動擁有⼀個 組號 ,規 分組0對應整個正則表達式
則是:從左向右,以分組的左括號為標誌,第 實際上組號分配過程是要從左向右掃
⼀個出現的分組的組號為1,第⼆個為2,以此 描兩遍的:第⼀遍只給未命名組分
類推。 配,第⼆遍只給命名組分配--因此
所有命名組的組號都⼤於未命名的組

你可以使⽤ (?:exp) 這樣的語法來剝奪
⼀個分組對組號分配的參與權.

後向引⽤ ⽤於重複搜索前⾯某個分組匹配的⽂本。例如, \1 代表 分組1匹配的⽂本 。


難以理解?請看⽰例:
\b(\w+)\b\s+\1\b 可以⽤來匹配 重複的單詞 ,像 go go , 或者 kitty kitty 。這個表達式⾸
先是 ⼀個單詞 ,也就是 單詞開始處和結束處之間的多於⼀個的字母或數字 ( \b(\w+)\b ),這
個單詞會被捕獲到編號為1的分組中,然後是 1個或幾個空⽩符 ( \s+ ),最後是 分組1中捕獲
的內容(也就是前⾯匹配的那個單詞) ( \1 )。
你也可以⾃⼰指定⼦表達式的 組名 。要指定⼀個⼦表達式的組名,請使⽤這樣的語
法: (?<Word>\w+) (或者把尖括號換成 ' 也⾏: (?'Word'\w+) ),這樣就把 \w+ 的組名指定為
Word 了。要反向引⽤這個分組 捕獲 的內容,你可以使⽤ \k<Word> ,所以上⼀個例⼦也可以
寫成這樣: \b(?<Word>\w+)\b\s+\k<Word>\b 。
使⽤⼩括號的時候,還有很多特定⽤途的語法。下⾯列出了最常⽤的⼀些:
表4.常⽤分組語法
分類 代碼/語法 說明
(exp) 匹配exp,並捕獲⽂本到⾃動命名的組裡
(?
捕獲 匹配exp,並捕獲⽂本到名稱為name的組裡,也可以寫成(?'name'exp)
<name>exp)
(?:exp) 匹配exp,不捕獲匹配的⽂本,也不給此分組分配組號
(?=exp) 匹配exp前⾯的位置

file:///Volumes/Zeit/Books/正則表達式30分鐘⼊⾨教程.webarchive Seite 10 von 18


正則表達式30分鐘⼊⾨教程 29.04.22, 00:27

零寬斷 (?<=exp) 匹配exp後⾯的位置


⾔ (?!exp) 匹配後⾯跟的不是exp的位置
(?<!exp) 匹配前⾯不是exp的位置
這種類型的分組不對正則表達式的處理產⽣任何影響,⽤於提供註釋讓⼈
註釋 (?#comment)
閱讀
我們已經討論了前兩種語法。第三個 (?:exp) 不會改變正則表達式的處理⽅式,只是這樣
的組匹配的內容 不會像前兩種那樣被捕獲到某個組裡⾯,也不會擁有組號 。“我為什麼會想
要這樣做?”——好問題,你覺得為什麼呢?

零寬斷⾔

接下來的四個⽤於查找在某些內容(但 斷⾔⽤來聲明⼀個應該為真的事實。
並不包括這些內容)之前或之後的東西,也 正則表達式中只有當斷⾔為真時才會繼續
就是說它們像 \b , ^ , $ 那樣⽤於指定⼀個 進⾏匹配。
位置,這個位置應該滿⾜⼀定的條件(即斷
⾔),因此它們也被稱為 零寬斷⾔ 。最好
還是拿例⼦來說明吧:
(?=exp) 也叫 零寬度正預測先⾏斷⾔ ,它 斷⾔⾃⾝出現的位置的後⾯能匹配表達式
exp 。⽐如 \b\w+(?=ing\b) ,匹配 以ing結尾的單詞的前⾯部分(除了ing以外的部分) ,如查
找 I'm singing while you're dancing. 時,它會匹配 sing 和 danc 。
(?<=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後⾯的單詞分隔符(可能是空格,或者是句號或其它

file:///Volumes/Zeit/Books/正則表達式30分鐘⼊⾨教程.webarchive Seite 11 von 18


正則表達式30分鐘⼊⾨教程 29.04.22, 00:27

的什麼),後⾯的 \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>) 匹配 不包含屬性的簡單HTML標籤內裡
的內容 。 (?<=<(\w+)>) 指定了這樣的 前綴 : 被尖括號括起來的單詞 (⽐如可能是<b>),然
後是 .* (任意的字符串),最後是⼀個 後綴 (?=<\/\1>) 。注意後綴裡的 \/ ,它⽤到了前⾯提過
的字符轉義; \1 則是⼀個反向引⽤,引⽤的正是 捕獲的第⼀組 ,前⾯的 (\w+) 匹配的內
容,這樣如果前綴實際上是<b>的話,後綴就是</b>了。整個表達式匹配的是<b>和</b>
之間的內容(再次提醒,不包括前綴和後綴本⾝)。

註釋
⼩括號的另⼀種⽤途是通過語法 (?#comment) 來包含註釋。例如: 2[0-4]\d(?#200-
249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199) 。
要包含註釋的話,最好是啟⽤“忽略模式裡的空⽩符”選項,這樣在編寫表達式時能任意
的添加空格,Tab,換⾏,⽽實際使⽤時這些都將被忽略。啟⽤這個選項後,在#後⾯到這⼀
⾏結束的所有⽂本都將被當成註釋忽略掉。例如,我們可以前⾯的⼀個表達式寫成這樣:
(?<= # 斷⾔要匹配的⽂本的前綴
<(\w+)> # 查找尖括號括起來的內容
# (即HTML/XML標籤)
) # 前綴結束
.* # 匹配任意⽂本
(?= # 斷⾔要匹配的⽂本的後綴
<\/\1> # 查找尖括號括起來的內容
# 查找尖括號括起來的內容
) # 後綴結束

貪婪與懶惰
當正則表達式中包含能接受重複的限定符時,通常的⾏為是(在使整個表達式能得到匹
配的前提下)匹配儘可能多的字符。以這個表達式為例: a.*b ,它將會匹配 最⾧的以a開
始,以b結束的字符串 。如果⽤它來搜索 aabab 的話,它會匹配整個字符串 aabab 。這被稱
為 貪婪 匹配。
有時,我們更需要 懶惰 匹配,也就是匹配儘可能少的字符。前⾯給出的限定符都可以被
轉化為懶惰匹配模式,只要在它後⾯加上⼀個問號 ? 。這樣 .*? 就意味著 匹配任意數量的重

file:///Volumes/Zeit/Books/正則表達式30分鐘⼊⾨教程.webarchive Seite 12 von 18


正則表達式30分鐘⼊⾨教程 29.04.22, 00:27

複,但是在能使整個匹配成功的前提下使⽤最少的重複 。現在看看懶惰版的例⼦吧:
a.*?b 匹配 最短的,以a開始,以b結 為什麼第⼀個匹配是aab(第⼀到第三
束的字符串 。如果把它應⽤於 aabab 的 個字符)⽽不是ab(第⼆到第三個字
話,它會匹配 aab(第⼀到第三個字符) 符)?簡單地說,因為正則表達式有另⼀
和 ab(第四到第五個字符) 。 條規則,⽐懶惰/貪婪規則的優先級更
⾼:最先開始的匹配擁有最⾼的優先權
——The match that begins earliest wins。
表5.懶惰限定符
代碼/語法 說明
*? 重複任意次,但儘可能少重複
+? 重複1次或更多次,但儘可能少重複
?? 重複0次或1次,但儘可能少重複
{n,m}? 重複n到m次,但儘可能少重複
{n,}? 重複n次以上,但儘可能少重複

處理選項

file:///Volumes/Zeit/Books/正則表達式30分鐘⼊⾨教程.webarchive Seite 13 von 18


正則表達式30分鐘⼊⾨教程 29.04.22, 00:27

上⾯介紹了幾個選項如忽略⼤⼩寫, 在C#中,你可以使⽤Regex(String,
處理多⾏等,這些選項能⽤來改變處理正 RegexOptions)構造函數來設置正則表達式
則表達式的⽅式。下⾯是.Net中常⽤的正 的處理選項。如:Regex regex = new
則表達式選項: Regex(@"\ba\w{6}\b",
表6.常⽤的處理選項 RegexOptions.IgnoreCase);
名稱 說明
匹配時不區分⼤⼩
IgnoreCase(忽略⼤⼩寫)
寫。
更改 ^ 和 $ 的含義,
使它們分別在任意
⼀⾏的⾏⾸和⾏尾
匹配,⽽不僅僅在
整個字符串的開頭
Multiline(多⾏模式)
和結尾匹配。(在此
模式下, $ 的精確含
意是:匹配\n之前的
位置以及字符串結
束前的位置.)
更改 . 的含義,使它
與每⼀個字符匹配
Singleline(單⾏模式)
(包括換⾏符
\n)。
忽略表達式中的⾮
IgnorePatternWhitespace(忽
轉義空⽩並啟⽤
略空⽩)
由 # 標記的註釋。
僅捕獲已被顯式命
ExplicitCapture(顯式捕獲)
名的組。
⼀個經常被問到的問題是:是不是隻 ⽬前(2019/06),只有基於
能同時使⽤多⾏模式和單⾏模式中的⼀ Webkit/Chromium 的瀏覽器(如 Chrome,
種?答案是:不是。這兩個選項之間沒有 Safari等)才⽀持 dotAll 選項。
任何關係,除了它們的名字⽐較相似(以
⾄於讓⼈感到疑惑)以外。事實上,為了
避免混淆,在最新的 JavaScript 中,單⾏
模式其實名叫 dotAll,意為點可以匹配所
有字符,然⽽在指定該選項時,⽤的還是
Singleline 的⾸字母 s.

平衡組/遞歸匹配

file:///Volumes/Zeit/Books/正則表達式30分鐘⼊⾨教程.webarchive Seite 14 von 18


正則表達式30分鐘⼊⾨教程 29.04.22, 00:27

有時我們需要匹配像 ( 100 * ( 50 + 15 ) 這裡介紹的平衡組語法是由.Net


)這樣的可嵌套的層次性結構 ,這時簡單 Framework⽀持的;其它語⾔/庫不⼀定⽀
地使⽤ \(.+\) 則只會匹配到最左邊的左括 持這種功能,或者⽀持此功能但需要使⽤
號和最右邊的右括號之間的內容(這裡我們 不同的語法。
討論的是貪婪模式,懶惰模式也有下⾯的
問題)。假如原來的字符串裡的左括號和右
括號出現的次數不相等,⽐如 ( 5 / ( 3 + 2
) ) ) ,那我們的匹配結果裡兩者的個數也
不會相等。有沒有辦法在這樣的字符串裡
匹配到最⾧的,配對的括號之間的內容
呢?
為了避免 ( 和 \( 把你的⼤腦徹底搞糊 如果你不是⼀個程序員(或者你⾃稱
塗,我們還是⽤尖括號代替圓括號吧。現 程序員但是不知道堆棧是什麼東西),你
在我們的問題變成了如何把 xx <aa <bbb> 就這樣理解上⾯的三種語法吧:第⼀個就
<bbb> aa> yy 這樣的字符串裡,最⾧的配 是在⿊板上寫⼀個"group",第⼆個就是從
對的尖括號內的內容捕獲出來? ⿊板上擦掉⼀個"group",第三個就是看⿊
這裡需要⽤到以下的語法構造: 板上寫的還有沒有"group",如果有就繼續
匹配yes部分,否則就匹配no部分。
(?'group') 把捕獲的內容命名為group,並壓
⼊ 堆棧(Stack)
(?'-group') 從堆棧上彈出最後壓⼊堆棧的
名為group的捕獲內容,如果堆棧本來為
空,則本分組的匹配失敗
(?(group)yes|no) 如果堆棧上存在以名為
group的捕獲內容的話,繼續匹配yes部分
的表達式,否則繼續匹配no部分
(?!) 零寬負向先⾏斷⾔,由於沒有後綴表
達式,試圖匹配總是失敗

我們需要做的是每碰到了左括號,就
在壓⼊⼀個"Open",每碰到⼀個右括號,就
彈出⼀個,到了最後就看看堆棧是否為空
--如果不為空那就證明左括號⽐右括號
多,那匹配就應該失敗。正則表達式引擎
會進⾏回溯(放棄最前⾯或最後⾯的⼀些字
符),儘量使整個表達式得到匹配。
< #最外層的左括號
[^<>]* #它後⾯⾮括號的內容
(
(
(?'Open'<) #左括號,壓⼊"Open"
[^<>]* #左括號後⾯的內容

file:///Volumes/Zeit/Books/正則表達式30分鐘⼊⾨教程.webarchive Seite 15 von 18


正則表達式30分鐘⼊⾨教程 29.04.22, 00:27

)+
(
(?'-Open'>) #右括號,彈出⼀個"Open"
[^<>]* #右括號後⾯的內容
)+
)*
(?(Open)(?!)) #最外層的右括號前檢查
#若還有未彈出的"Open"
#則匹配失敗

> #最外層的右括號

平衡組的⼀個最常⾒的應⽤就是匹配
HTML,下⾯這個例⼦可以匹配 嵌套的
<div>標籤 : <div[^>]*>[^<>]*
(((?'Open'<div[^>]*>)[^<>]*)+((?'-
Open'</div>)[^<>]*)+)*(?(Open)(?!))
</div> .

還有些什麼東西沒提到
上邊已經描述了構造正則表達式的⼤量元素,但是還有很多沒有提到的東西。下⾯是⼀
些未提到的元素的列表,包含語法和簡單的說明。你可以在網上找到更詳細的參考資料來學
習它們--當你需要⽤到它們的時候。如果你安裝了MSDN Library,你也可以在裡⾯找到.Net下
正則表達式詳細的⽂檔。這裡的介紹很簡略,如果你需要更詳細的信息,⽽又沒有在電腦上
安裝MSDN Library,可以查看關於正則表達式語⾔元素的MSDN在線⽂檔。
表7.尚未詳細討論的語法
代碼/語法 說明
\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 字符串開頭(類似^,但不受處理多⾏選項的影響)

file:///Volumes/Zeit/Books/正則表達式30分鐘⼊⾨教程.webarchive Seite 16 von 18


正則表達式30分鐘⼊⾨教程 29.04.22, 00:27

\Z 字符串結尾或⾏尾(不受處理多⾏選項的影響)
\z 字符串結尾(類似$,但不受處理多⾏選項的影響)
\G 當前搜索的開頭
\p{name} Unicode中命名為name的字符類,例如\p{IsGreek}
(?>exp) 貪婪⼦表達式
(?<x>-
平衡組
<y>exp)
(?im-nsx:exp) 在⼦表達式exp中改變處理選項
(?im-nsx) 為表達式後⾯的部分改變處理選項
(? 把exp當作零寬正向先⾏斷⾔,如果在這個位置能匹配,使⽤yes作為此組的表達
(exp)yes|no) 式;否則使⽤no
(?(exp)yes) 同上,只是使⽤空表達式作為no
(?
如果命名為name的組捕獲到了內容,使⽤yes作為表達式;否則使⽤no
(name)yes|no)
(?(name)yes) 同上,只是使⽤空表達式作為no

聯繫作者
好吧,我承認,我騙了你,讀到這裡你肯定花了不⽌30分鐘。相信我,這是我的錯,⽽
不是因為你太笨。我之所以說"30分鐘",是為了讓你有信⼼,有耐⼼繼續下去。既然你看到
了這裡,那證明我的陰謀成功了。被忽悠的感覺很爽吧?
要投訴我,或者覺得我其實可以忽悠得更⾼明,或者有關於正則表達式的問題, 可以發
郵件到 deerchao#qq#com。如果本⽂給了你幫助,你可以使⽤⽀付寶或微信⽀付向我打賞。
點擊本⾴右上⽅的“打賞”即可看到⽀付⼆維碼,可能你得先回到⾴⾯最頂端。

網上的資源及本⽂參考⽂獻
精通正則表達式(第3版)
微軟的正則表達式教程
Regex類(微軟⽂檔)
專業的正則表達式教學網站(英⽂)
關於.Net下的平衡組的詳細討論(英⽂)

更新紀錄
1. 2006-3-27 第⼀版
2. 2006-10-12 第⼆版
修正了幾個細節上的錯誤和不準確的地⽅

file:///Volumes/Zeit/Books/正則表達式30分鐘⼊⾨教程.webarchive Seite 17 von 18


正則表達式30分鐘⼊⾨教程 29.04.22, 00:27

增加了對處理中⽂時的⼀些說明
更改了幾個術語的翻譯(採⽤了MSDN的翻譯⽅式)
增加了平衡組的介紹
放棄了對The Regulator的介紹,改⽤Regex Tester
3. 2007-3-12 V2.1
修正了幾個⼩的錯誤
增加了對處理選項(RegexOptions)的介紹
4. 2007-5-28 V2.2
重新組織了對零寬斷⾔的介紹
刪除了幾個不太合適的⽰例,添加了幾個實⽤的⽰例
其它⼀些微⼩的更改
5. 2007-8-3 V2.21
修改了幾處⽂字錯誤
修改/添加了對$,\b的精確說明
承認了作者是個騙⼦
給RegexTester添加了Singleline選項的相關功能
6. 2008-4-13 v2.3
調整了部分章節的次序
修改了⾴⾯佈局,刪除了專⾨的參考節
針對讀者的反饋,調整了部分內容
7. 2009-4-11 v2.3.1
修改了幾處⽂字錯誤
添加了⼀些註釋說明
調整了⼀些措詞
8. 2011-8-17 v2.3.2
更改了⼯具介紹,換⽤⾃⾏開發的正則表達式測試器
9. 2013-1-10 v2.3.3
說明包含前導0的IP地址是合法的
10. 2017-6-6 v2.3.4
更新測試⼯具
11. 2017-6-12 v2.3.5
修復分⽀條件章節下的錯誤(刪除括號後的問號)
12. 2019-6-28 v2.4
提供在線 Javascript 正則表達式測試⼯具
提到 Javascript 中的 dotAll 模式
修改作者聯繫⽅式
13. 2019-11-15 v2.4.1
改進在⼿機瀏覽器下的⾴⾯佈局

file:///Volumes/Zeit/Books/正則表達式30分鐘⼊⾨教程.webarchive Seite 18 von 18

You might also like