本文譯自 制作正則引擎的作者 Jan Goyvaerts 為工具 RegexBuddy 寫的教程
版權歸原作者所有
注意:本頁面存在bug哩治,有的字符被過濾。
表達式測試網(wǎng)站 https://regex101.com/
正則表達式
正則表達式是一種用來描述一定數(shù)量文本的模式程储。
RegEx 代表 Regular Express
正則表達式引擎
是一種可以處理正則表達式的軟件(通常引擎是更大的應用程序的一部分)
本教程集中討論應用最廣泛的 Perl 5 類型的引擎韭赘。對比其他引擎的區(qū)別。
近代引擎都很類似但不完全相同 如 .NET 正則庫 JDK 正則包
文字符號
最簡單的正則表達式由單個文字符號組成翘县。
如a
將匹配字符串中第一次出現(xiàn)的字符a
诺擅。
匹配字符串Jack is a boy
市袖。
結果:J
后的a
將被匹配。而第二個a
將不會被 匹配烁涌。
正則表達式也可以匹配第二個a
苍碟,這必須是你告訴正則表達式引擎:從第一次匹配的地方開始搜索。
(相當于文本編輯器中的查找下一個
撮执。也相當于編程中有一個函數(shù)從前一次匹配的位置開始繼續(xù)向后搜索微峰。)
cat
會匹配About cats and dogs
中的cat
。
等于是告訴正則表達式引擎:
找到一個c
二打,緊跟一個a
县忌,再跟一個t
正則表達式引擎默認大小寫敏感
除非你告訴引擎忽略大小寫掂榔,否則cat 不會匹配Cat
继效。
元字符(metacharacter)
對于文字字符,有 12 個字符被保留作特殊用途装获,即 元字符:
[ ]\^ $. |? *+ ()
在正則表達式中,如果要將這些字符用作無特殊含義的文本字符
用反斜杠對其進行轉義 (escape)
例如
表達式為1\+1=2
匹配1+1=2
注意正則表達式1+1=2
是有效的 +
表示重復 1 次到多次
它不會匹配文本1+1=2
會匹配文本123+111=234
中的111=2
在編程語言中瑞信,一些特殊的字符會先被編譯器處理,然后再傳遞給正則引擎穴豫。
#因此正則表達式
1\+2=2
#在C++中要寫成
1\\+1=2
#為了匹配到文本 C:\temp
#正則表達式 C:\\temp
#C++中的正則表達式 C:\\\\temp
不可顯示字符
某些不可顯示字符 使用它對應的 特殊字符序列
\t Tab 0x09
\r 回車符 0x0D
\n 換行符 0x0A
#注意
Windows 中文本文件結束一行用\r\n
Unix 使用\n
正則表達式引擎的內部工作機制
正則表達式引擎是如何工作的有助于你很快理解為何某個正則表達式不像你期望的那樣工作凡简。
有2種類型的引擎:
- 文本導向(text-directed)的引擎
- 正則導向(regex-directed)的引擎 (目前最流行,本文談的就是正則導向的引擎)
Jeffrey Friedl 把他們稱作 DFA 和 NFA 引擎精肃。
因為一些非常有用的特性只能在正則導向的引擎中實現(xiàn):
如
惰性量詞(lazy quantifiers)
反向引用(backreferences)
通過測試 判斷 某引擎 文本導向/正則導向:
如果 反向引用 或 惰性量詞秤涩,被實現(xiàn)則引擎必定是正則導向
正則表達式regex|regex not
對字符串regex not
進行匹配,得到結果:
結果1 regex not
則 該引擎是文本導向的司抱。
結果2 regex
則 該引擎是正則導向的筐眷。正則導向的引擎總是很急切地報告它找到的第一個匹配
正則導向的引擎 -> 急切的返回第1個(即最左邊)匹配結果
這是需要你理解的很重要的一點,即使以后有可能發(fā)現(xiàn)更好的匹配习柠,正則導向的引擎也總是返回最左邊的匹配匀谣。
例
正則表達式cat
文本He captured a catfish for his cat
詳細匹配過程:
引擎先比較正則表達式符號c
和H
照棋,匹配失敗武翎;
于是引擎再比較正則表達式符號c
和e
烈炭,匹配失敗;
... 直到匹配第4個字符c
,匹配成功;
正則表達式符號a
匹配第5個字符a
,匹配成功宝恶;
第六個字符t
匹配p
符隙,匹配失敗垫毙;
引擎繼續(xù)從第5個字符重新檢查匹配性
...直到第15個字符開始膏执,cat
匹配上了catfish
中的cat
就此結束。正則表達式引擎急切返回第1個匹配結果露久,而不會再繼續(xù)向后查找(無論是否有其他文本可以被匹配
字符集
字符集是由一對方括號[]
括起來的字符集合更米。
使用字符集,告訴正則表達式引擎僅匹配多個字符中的一個毫痕。
使用正則表達式[ae]
可匹配一個a
或一個e
字符集中的字符順序并沒有意義征峦,結果都是相同的。
如
使用正則表達式gr[ae]y
或等價的gr[ea]y
都可匹配 gray 或 grey (在你不確定待匹配的文本是 美/英 英語時特別有用)
都不會匹配 graay 或 graey
用帶連字符-
的字符集 定義字符范圍
如 字符集 [0-9]
匹配 0 到 9 之間的某個單個數(shù)字消请。
使用多個范圍的字符集
如
匹配某個單個的十六進制的字符 且 大小寫不敏感
[0-9a-fA-F]
同時使用:結合范圍定義 與 單個字符定義
例
[0-9a-fxA-FX]
如
匹配某個單個的十六進制的字符 且 大小寫不敏感栏笆,或字符X
再次強調,字符和范圍定義的先后順序 對結果沒有影響臊泰。
字符集的一些應用
查找一個可能有拼寫錯誤的單詞
比如sep[ae]r[ae]te
或 li[cs]en[cs]e
查找程序語言的標識符
A-Za-z_][A-Za-z_0-9]*
*表示重復 0 或多次
查找 C 風格的十六進制數(shù)
0[xX][A-Fa-f0-9]+
+表示重復(上一個字符或表達式)一次或無數(shù)次
取反字符集
在左方括號[
后面緊跟一個^
將會對字符集取反蛉加,這樣的正則表達式表示將匹配任何不在方括號中的字符。
注意 取反字符集一定會要 匹配一個字符缸逃,可以匹配回車換行符
例1
匹配一個 q针饥,后面跟著一個非 u 的字符
正則表達式 q[^u]
文本
ffffq
fffff
結果
q 和一個換行符 (非 u 的字符
回車換行符 是匹配結果中的一部分)
例2
正則表達式:q[^u]
文本: Iraq
進行匹配 得不到任何結果
因為q后沒有任何字符!單個q不會被匹配
如果只想匹配某些條件下的 q字符需频,如條件是 q 后面有一個非 u 的字符時才匹配這個q字符丁眼,則用后面講到的向前查看。
字符集中的元字符
注意昭殉,在字符集中只有 4 個 元字符] \ ^ -
]代表字符集定義的結束
\代表轉義
^代表取反
-代表范圍定義
除了這4個元字符外的其他常見元字符苞七,在字符集定義內部都是文本字符,不需要轉義(如果對那些通常的元字符進行轉義挪丢,正則表達式一樣會工作得很好蹂风,但會降低人類可讀性)
例,要匹配文本 星號*或加號+
正則表達式 [+*]
在字符集定義中,為了將反斜杠作為一個文字字符乾蓬,而非特殊含義的字符惠啄,你需要用另一個反斜杠對它進行轉義。
正則表達式[\\x]
會匹配到文本\x
元字符]^-
都可以用反斜杠進行轉義,或將它們放在一個不可能使用到他們特殊含義的位置(這樣可以增加可讀性)
比如對于字符^
將它放在非左括號[
后面的位置礁阁,使用的是文本字符含義巧号,而非取反含義。
如
[x^]
會匹配一個字符 x
或^
[]x]
會匹配一個字符 ]
或x
[-x]
或[x-]
會匹配一個-
或x
字符集的簡寫【記憶】
很常用的字符集有簡寫方式
\d
代表數(shù)字[0-9]
\w
代表單詞字符姥闭,隨正則表達式實現(xiàn)的不同丹鸿,有些差異。絕大多數(shù)的正則表達式實現(xiàn)的單詞字符集都包含了A-Za-z0-9_
\s
代表空白字符
隨正則表達式實現(xiàn)的不同而有些差異棚品。在絕大多數(shù)的實現(xiàn)中靠欢,都包含了空格符和 Tab 符,以及回車換行符\r\n
方括號內外都可以用字符集的縮寫形式
\s\d
匹配 某單個白字符后面緊跟一個數(shù)字
[\s\d]
匹配 某單個白字符或數(shù)字
[\da-fA-F]
匹配一個十六進制數(shù)字
取反字符集的簡寫形式
[\S]
即 [^\s]
[\W]
即 [^\w]
[\D]
即 [^\d]
字符集的重復
如果用?*+
操作符來重復一個字符集铜跑,將會重復整個字符集(而不是它匹配的那個字符)
例
正則表達式[0-9]+
會匹配文本 837 以及 222
如果你僅僅想重復被匹配的那個字符门怪,可以用向后引用。以后說
使用?*或+ 進行重復
?
告訴引擎匹配前導字符 0 次或一次锅纺。事實上是表示前導字符是可選的掷空。
告訴引擎匹配前導字符 1 次或多次
告訴引擎匹配前導字符 0 次或多次
例
<[A-Za-z][A-Za-z0-9]*>
匹配沒有屬性的 HTML 標簽,<
以及>
是文字符號囤锉。
第一個字符集匹配一個字母坦弟,第二個字符集匹配一個字母或數(shù)字。
- 代表匹配前導字符0或無數(shù)次官地,0次時可以匹配像<p>這樣的標簽酿傍。
我們似乎也可以用正則表達式 <[A-Za-z0-9]+>
但它會匹配<1>
限制重復次數(shù)
對前導子表達式重復多少次 語法{min,max}
min 和 max 都是非負整數(shù)(整數(shù)min>=0 整數(shù)max>=0)
例1
{0,1}
對前導子表達式重復0次 或 1次
{1}
對前導子表達式重復1次 如果逗號和max都省略時則重復 min 次
{0,}
等價于*
有逗號 不寫max值 表示 max值 無限大
{1,}
等價于+
例2
正則表達式zo+
能匹配zo
和zoo
. 但不能匹配z
正則表達式\b[1-9][0-9]{3}\b
(\b表示單詞邊界)
匹配某個 1000~9999 之間的數(shù)字。
正則表達式\b[1-9][0-9]{2,4}\b
匹配某個 100~99999 之間的數(shù)字驱入。
注意: 懶惰指匹配結果盡可能短赤炒、貪婪是匹配結果盡可能長
懶惰的?
匹配盡可能短的文本
貪婪的+
匹配盡可能長的文本
例
用一個正則表達式匹配一個 HTML 標簽。
輸入一個正常有效的 HTML 文件(不需要排除無效標簽),所以如果是在兩個尖括號之間的內容亏较,就應該是一個 HTML 標簽莺褒。
貪婪的正則表達式<.+>
匹配結果盡可能長
許多新手用表達式<.+>
測試字符串This is a <EM>first</EM> test
得到匹配結果<EM>first</EM>
很顯然不是我們想要的結果。
我們期望得到結果<EM>
繼續(xù)進行匹配 得到</EM>
因為+
是貪婪的 即匹配盡量長的文本宴杀!
+
會導致正則表達式引擎試圖盡可能的重復它前面的那個字符(只有當這種重復會引起整個正則表達式匹配失敗的情況下癣朗,引擎會進行回溯,即放棄最后一次的重復旺罢,然后處理正則表達式余下的部分)
和+
類似,?*
的重復也是貪婪的绢记。
詳細匹配過程:
<
匹配字符<
,匹配成功扁达;
.
匹配了字符E
,匹配成功蠢熄;
+
重復上一個(表達式符號)跪解,即表達式.
點號 可以一直匹配接下來的字符,直到碰到換行符签孔,.
不匹配換行符則.
匹配失敗叉讥,即+
匹配失斁叫小;
此時已匹配到的文本為<EM>first</EM> test
引擎對正則表達式最后一個符號>
進行匹配,試圖將>
與換行符進行匹配图仓,>
匹配失敼蘅;
此時已匹配到的文本為<EM>first</EM> test
表達式最后一個符號>
也匹配失敗了救崔,引擎進行回溯(從向后匹配改為向前匹配)
>
試圖與t
匹配惶看,匹配失敗六孵;
>
繼續(xù)往前匹配纬黎,繼續(xù)失敗..
直到遇見<EM>first</EM
后面的字符>
,匹配成功劫窒!
第1個匹配結果為
<EM>first</EM>
正則導向的引擎是急切的
,所以它會急著報告它找到的第一個匹配本今。而不是繼續(xù)回溯(所以匹配不到<EM>
了)
能看出+
的貪婪性使正則表達式引擎得到了一個“最長”的匹配結果。
可行方案1 使用“懶惰的”正則表達式<.+?>
匹配標簽
在+后面緊跟一個問號?實現(xiàn)懶惰性(匹配盡量短的文本)
*``{}``?
表示的重復也可以用類似的方案主巍,使貪婪性變成懶惰性诈泼,匹配盡量短的文本。
?
匹配盡量短的文本
+?
匹配盡量短的文本煤禽!實現(xiàn)了"懶惰性" 帶上問號的+是懶惰的铐达!
+
為了盡可能少地重復上一個字符.
所以匹配成功后立即用下一個正則表達式符號進行匹配
正則表達式<.+?>
詳細匹配過程:
正則表達式符號.
匹配文本中的下一個字符E
匹配成功;
懶惰的+?
為了盡快得到匹配結果,用下一個正則表達式符號>
匹配文本中下一個字符M
檬果,匹配失斘退铩(引擎進行回溯)
用上一個正則表達式字符.
匹配當前字符M
,匹配成功;
此時正則表達式進行到<.+
此時已匹配到的文本為<EM
懶惰的+?
為了盡快得到匹配結果选脊,立即用下一個正則表達式符號>
匹配文本中下一個字符>
匹配成功;
正則表達式結束
引擎報告得到一個成功的匹配<EM>
更好的替代方案:取反字符集
正則導向的引擎
用一個取反字符集 跟 一個貪婪重復 加號<[^>]+>
之所以說這是一個更好的方案杭抠,因為使用取反字符集,不需要進行回溯
在于使用惰性重復時恳啥,引擎會在找到一個成功匹配前對每一個字符進行回溯偏灿。而
使用.匹配任意字符 (除了換行符)
在正則表達式中.
是最常用也最容易被誤用的符號。
.
匹配任意單個字符(唯一不匹換行符)
在本教程中談到的引擎钝的,默認都不匹配換行符
默認.
等價于
字符集[^\n\r]
(Windows)
字符集[^\n]
(Unix)
不匹配換行符是因為歷史的原因:
早期使用正則表達式的工具是基于行的翁垂。它們都是一行一行的讀入一個文件,將正則表達式分別應用到每一行上去硝桩。
所以每一行的字符串里當然沒有換行符沿猜。
所以.
不匹配換行符。
現(xiàn)代的工具和語言能夠將正則表達式應用到很大的字符串,甚至整個文件,所有正則表達式實現(xiàn)都提供一個選項:可讓.
匹配所有的字符(包括換行符)
RegexBuddy,EditPad Pro 或 PowerGREP 等工具中碗脊,你可以簡單的選中點號匹配換行符
啼肩。
在 Perl 中,.
可以匹配換行符的模式被稱作單行模式
,這是一個很容易混淆的名詞祈坠。因為還有多行模式
害碾。
多行模式只影響行首行尾的錨定(anchor),而單行模式只影響.
其他語言和正則表達式庫也采用了 Perl 的術語定義赦拘。
當在.NET Framework 中使用正則表達式類時慌随,可用類似下面的語句來激活單行模式:
Regex.Match(string
,regex
,RegexOptions.SingleLine)
強大的點號.
點號可以說是最強大的元字符:用一個點號,匹配幾乎所有的字符另绩。
問題是 它也常常會匹配不該匹配的字符儒陨!
例子
匹配一個具有mm/dd/yy
格式的 月/天/年 日期
允許用戶來選擇分隔符。
不可行方案1
\d\d.\d\d.\d\d
它能匹配日期02/12/03
問題是 02512703
也會被匹配
不可行方案2
\d\d[-/.]\d\d[-/.]\d\d
稍微好一點笋籽,支持3種分隔符蹦漠。
(點號在一個字符集里,不作元字符)
這個方案遠不夠完善,它會匹配99/99/99
方案3
[0-1]\d[-/.][0-3]\d[-/.]\d\d
更好一點车海,盡管會匹配19/39/99
...
如果你想校驗用戶輸入笛园,則需要盡可能的完美。
如果你只是想分析一個已知的源侍芝,并且我們知道沒有錯誤的數(shù)據(jù)研铆,用一個比較好的正則表達式,來匹配你想要搜尋的字符就已經(jīng)足夠。
字符串開始的錨定^ 和 結束的錨定$
錨定和一般的正則表達式符號不同州叠,錨定不匹配任何字符棵红。
錨定匹配的是字符之前或之后的位置
正則表達式^
會匹配一行字符串第一個字符前的位置
正則表達式^a
會匹配字符串abc
中的 a
正則表達式^b
不會匹配abc
中的任何字符
類似的,$匹配字符串中最后一個字符的后面的位置
#正則表達式
c$
#匹配文本abc中的c
錨定的應用
使用錨定在編程語言中校驗用戶輸入 非常重要的
校驗用戶的輸入為整數(shù) 用正則表達式^\d+$
匹配用戶輸入中常常存在的多余的前導空格或結束空格
^\s*
匹配字符串前面的空格
\s*$
匹配字符串末尾的空格
如果你有一個包含了多行的字符串 如
first line\n\rsecond line
(其中\(zhòng)n\r 表示一個換行符)
經(jīng)常需要按行處理咧栗,而不是整個字符串逆甜。
幾乎所有的正則表達式引擎都提供一個選項,可以擴展這兩種錨定的含義致板。
^
可以匹配字串的開始位置(在 f 之前)交煞,以及每一個換行符后面的位置(這個位置在\n\r 和 s 之間)
$
會匹配字串的結束位置(最后一個 e之后),以及每個新行符之前的位置(這個位置在 e 與\n\r 之間)
在.NET 中使用如下代碼定義錨定斟或,匹配每一個換行符的前面和后面位置:
Regex.Match("string", "regex", RegexOptions.Multiline)
例
在每行的行首插入文本字符>
string str = Regex.Replace(Original, "^", ">", RegexOptions.Multiline)
絕對錨定
\A
只匹配整個字符串的開始位置
\Z
只匹配整個字符串的結束位置
即使你使用了多行模式素征,這兩個也絕不匹配換行符。
例外
如果字符串以換行符(newline)結束萝挤,則\Z
和$
將會匹配這個最后的換行符前面的位置御毅,而不是整個字符串的最后面。
這個改進是由 Perl 引進的平斩,然后被許多的正則表達式實現(xiàn)所遵循亚享,包括 Java,.NET 等绘面。
例
正則表達式:^[a-z]+$
文本:joe\n
匹配結果:joe
而不是joe\n