前言
正則表達(dá)式(Regular Expression,RegExp,regex)使用單個(gè)字符串來(lái)描述和匹配一系列符合某種句法規(guī)則的字符串。此概念來(lái)自形式化語(yǔ)言理論,最初由貝爾實(shí)驗(yàn)室實(shí)現(xiàn)。正則表達(dá)式最初在 Perl 中實(shí)現(xiàn),它的推廣得益于 UNIX 軟件的流行,尤其是 SED,GREP 等。 現(xiàn)在許多編程語(yǔ)言都內(nèi)置了正則表達(dá)式引擎,如 PERL、Python、JAVAScript、Java、C++ 等。很多文本編輯器也支持正則表達(dá)式來(lái)進(jìn)行檢索和替換,如 Vim、Sublime Text、Visual Studio Code 等。正則表達(dá)式相關(guān)的學(xué)習(xí)文章網(wǎng)上也是一大推,本文主要記錄正則表達(dá)式的入門(mén)教程和常用公式工具,方便大家活學(xué)活用。
為什么要正則表達(dá)式?
為什么需要正則表達(dá)式 - 王垠
學(xué)習(xí) Unix 最開(kāi)頭,大家都學(xué)過(guò)正則表達(dá)式 (regexp)。可是有沒(méi)有人考慮過(guò)我們?yōu)槭裁葱枰齽t表達(dá)式?
正則表達(dá)式本來(lái)的初衷是用來(lái)從無(wú)結(jié)構(gòu)的字符串中提取信息,殊不知這正好是 Unix 的缺陷所在。Unix 用無(wú)結(jié)構(gòu)的字符串來(lái)表示數(shù)據(jù),導(dǎo)致了諸多復(fù)雜的基于 regexp 的軟件的誕生。sed, AWK, Perl, … 都是為了同樣的目的來(lái)到這個(gè)世界上的。如果不是因?yàn)?Unix 用字符串來(lái)表示數(shù)據(jù),我們就會(huì)擁有按數(shù)據(jù)結(jié)構(gòu)類型的直接存儲(chǔ),而不需要折騰 regexp。正則表達(dá)式有它自己的價(jià)值(針對(duì)自然語(yǔ)言),但是我們其實(shí)不需要把它應(yīng)用到程序語(yǔ)言和操作系統(tǒng)里面。
正則表達(dá)式本身用一個(gè)字符串來(lái)表示,這帶來(lái)另外一些問(wèn)題。因?yàn)檎齽t表達(dá)式的本質(zhì)不是字符串,而是一個(gè)數(shù)據(jù)結(jié)構(gòu)。學(xué)過(guò)計(jì)算理論的人可能知道這個(gè)數(shù)據(jù)結(jié)構(gòu)叫做 NFA(nondeterministic finite automaton,非確定性有限自動(dòng)機(jī))。所有的數(shù)據(jù)結(jié)構(gòu)應(yīng)該由程序語(yǔ)言本身來(lái)表示,就像用 Java 構(gòu)造一個(gè)對(duì)象用 new ClassA("a") 一樣。但是正則表達(dá)式強(qiáng)迫你把這個(gè)簡(jiǎn)單的構(gòu)造函數(shù)調(diào)用寫(xiě)成一個(gè)字符串。所以在這個(gè)比方之下,你得寫(xiě)成 new ClassA("a")。這樣當(dāng)你想要組合這些表達(dá)式的時(shí)候就發(fā)現(xiàn),正則表達(dá)式幾乎都是不可組合 (compose) 的。你幾乎不可能不能把兩個(gè) regexp 的變量 A 和 B 安全拼接成一個(gè),比如用 Java 的字符串拼接 A+B。因?yàn)槟悴恢肋@兩個(gè)字符串拼在一起之后,那些稀奇古怪的符號(hào)會(huì)出現(xiàn)什么交叉反應(yīng),使得最后的識(shí)別的東西根本不是你想要的。
在正則表達(dá)式中,由于正則表達(dá)式本身的構(gòu)造函數(shù)與數(shù)據(jù)本身合并到一起,我們不得不對(duì)某些 “特殊字符” 進(jìn)行 escape。這些特殊字符,其實(shí)是用來(lái)描述 NFA 的記號(hào),它們屬于更高一層的語(yǔ)言。可是在正則表達(dá)式里,它們與 NFA 節(jié)點(diǎn)里的字符混為一談。比如很簡(jiǎn)單的一個(gè) block comment 的正則表達(dá)式,卻要寫(xiě)成這個(gè)樣子:
/\\*([^\\*]|[^/])*\\*/
顯然這樣的表達(dá)式很容易出錯(cuò)。 如果我們用程序語(yǔ)言的表達(dá)式來(lái)構(gòu)造這個(gè)表達(dá)式,它應(yīng)該是這樣:
(@... "/*" (@*(@!"*/")) "*/")
在這個(gè)我自己設(shè)計(jì)的 Scheme 表達(dá)式里,以 @開(kāi)頭的標(biāo)識(shí)符都是構(gòu)造函數(shù)。其中 @... 是構(gòu)造 sequence,@* 是構(gòu)造一個(gè) zero-or-more 的匹配,@! 構(gòu)造一個(gè)否定匹配。這個(gè)表達(dá)式是說(shuō):“以 / * 開(kāi)頭,接著零個(gè)或者多個(gè)不是 * / 的字符,最后接著一個(gè) * /。這樣一來(lái)清晰明了,什么表達(dá)式在什么 “層次” 都很清楚,不需要什么反斜杠 escape,而且這樣的表達(dá)式可以 compose。比如:
(define reg1 (@... "/*" (@*(@!"*/")) "*/")) (define reg2 (@+ "foo")) (define reg3 (@= "b"))
定義這三個(gè)表達(dá)式之后,我們之后可以用像 (@... reg1 (@or reg2 reg3)) 這樣的表達(dá)式來(lái)連接 3 個(gè)不同的表達(dá)式,構(gòu)造出更大的表達(dá)式。這樣的構(gòu)造可以無(wú)限的擴(kuò)展。從這里以及以往的經(jīng)驗(yàn),我總結(jié)出一個(gè)普遍適用的程序設(shè)計(jì)的教訓(xùn):盡量不要把多個(gè)層次的語(yǔ)言 “壓縮” 到一層。我們也看到正則表達(dá)式與 “Unix 哲學(xué)” 有很大關(guān)系。我沒(méi)有考古,所以不知道孰先孰后,但是它們肯定有直接的因果關(guān)系。兩者都是 Unix 復(fù)雜性的來(lái)源。
再來(lái)看取自 12306 網(wǎng)站的一段代碼
// http://www.12306.cn/mormhweb/js/adKyfw.min.js d = d.replace("'", ""); d = d.replace("%", ""); d = d.replace("#", ""); d = d.replace("&", ""); d = d.replace("*", ""); d = d.replace("(", ""); d = d.replace(")", ""); d = d.replace("@", ""); d = d.replace("`", ""); d = d.replace("/", ""); d = d.replace("\", ""); d = d.replace(",", ""); d = d.replace(".", ""); d = d.replace("=", ""); d = d.replace("<", ""); d = d.replace(">", "");
上述代碼是在過(guò)濾掉不合法的搜索字符(姑且不論客戶端過(guò)濾是否安全), 我們可以用一行正則替換來(lái)實(shí)現(xiàn)相同的功能:
d = d.replace(/'%#&*()@`/\,.=<>/g, '');
正則表達(dá)式入門(mén)教程推薦
感謝作者 deerchao 從 2006 年開(kāi)始更新至今,謝謝
正則表達(dá)式 30 分鐘入門(mén)教程 - DeerChao
正則表達(dá)式 - 教程
Python RegEx
正則表達(dá)式在線工具
regexr
regex101
正則表達(dá)式測(cè)試工具(在線)
正則表達(dá)式在線測(cè)試
正則表達(dá)式基本語(yǔ)法
定義正則表達(dá)式的方式在不同的工具中可能有所差別,但正則表達(dá)式內(nèi)容的語(yǔ)法是一致的。 正則表達(dá)式有三類語(yǔ)法結(jié)構(gòu):
- 串接(與操作)。相鄰的字符默認(rèn)為串接關(guān)系。例如 harttle 只能匹配 harttle, 不可匹配 hart。
- 選擇(或操作,|)。例如:harttle|serene 可以匹配 harttle 或者 serene。 選擇的優(yōu)先級(jí)級(jí)低于串接,因此很多情況下都可以省略括號(hào)。
- 數(shù)量(限定符)。最常見(jiàn)的數(shù)量限定符包括 +, ?, *,分別表示左側(cè)的字符出現(xiàn)一次或更多,不出現(xiàn)或出現(xiàn)一次,不出現(xiàn)或出現(xiàn)任意次。例如 harttle? 可以匹配 harttl 和 harttle。
- 組合(括號(hào),())。組合用來(lái)定義操作符的作用范圍和優(yōu)先級(jí)。例如 har(ttle)? 可以匹配 harttle 和 har,h(a|u)rttle 可以匹配 harttle 和 hurttle。
常用正則表達(dá)式
字符 描述 \ 將下一個(gè)字符標(biāo)記為一個(gè)特殊字符、或一個(gè)原義字符、或一個(gè)向后引用、或一個(gè)八進(jìn)制轉(zhuǎn)義符。例如,”n“匹配字符”n“。”n“匹配一個(gè)換行符。序列”“匹配”“而” (“則匹配”(“。 ^ 匹配輸入字符串的開(kāi)始位置。如果設(shè)置了 RegExp 對(duì)象的 Multiline 屬性,^ 也匹配”n“或”r“之后的位置。 $ 匹配輸入字符串的結(jié)束位置。如果設(shè)置了 RegExp 對(duì)象的 Multiline 屬性,$ 也匹配”n“或”r“之前的位置。 * 匹配前面的子表達(dá)式零次或多次。例如,zo 能匹配”z“、”zo“以及”zoo“。等價(jià)于 {0,}。 + 匹配前面的子表達(dá)式一次或多次。例如,”zo+“能匹配”zo“以及”zoo“,但不能匹配”z“。+ 等價(jià)于 {1,}。 ? 匹配前面的子表達(dá)式零次或一次。例如,”do(es)?“可以匹配”do“或”does“中的”do“。? 等價(jià)于 {0,1}。 {n} n 是一個(gè)非負(fù)整數(shù)。匹配確定的 n 次。例如,”o{2}“不能匹配”Bob“中的”o“,但是能匹配”food“中的兩個(gè) o。 {n,} n 是一個(gè)非負(fù)整數(shù)。至少匹配 n 次。例如,”o{2,}“不能匹配”Bob“中的”o“,但能匹配”foooood“中的所有 o。”o{1,}“等價(jià)于” o+“。”o{0,}“則等價(jià)于”o“。 {n,m} m 和 n 均為非負(fù)整數(shù),其中 n<=m。最少匹配 n 次且最多匹配 m 次。例如,”o{1,3}“將匹配”fooooood“中的前三個(gè) o。”o{0,1}“等價(jià)于”o?“。請(qǐng)注意在逗號(hào)和兩個(gè)數(shù)之間不能有空格。 ? 當(dāng)該字符緊跟在任何一個(gè)其他限制符(*,+,?,{n},{n,},{n,m})后面時(shí),匹配模式是非貪婪的。非貪婪模式盡可能少的匹配所搜索的字符串,而默認(rèn)的貪婪模式則盡可能多的匹配所搜索的字符串。例如,對(duì)于字符串”oooo“,”o+?“將匹配單個(gè)”o“,而” o+“將匹配所有”o“。 . 匹配除”n“之外的任何單個(gè)字符。要匹配包括”n“在內(nèi)的任何字符,請(qǐng)使用像” (.|n) “的模式。 (pattern) 匹配 pattern 并獲取這一匹配的子字符串。該子字符串用于向后引用。所獲取的匹配可以從產(chǎn)生的 Matches 集合得到,在 VBScript 中使用 SubMatches 集合,在 JScript 中則使用 $0...$9 屬性。要匹配圓括號(hào)字符,請(qǐng)使用”(“或”)“。 (?:pattern) 匹配 pattern 但不獲取匹配的子字符串,也就是說(shuō)這是一個(gè)非獲取匹配,不存儲(chǔ)匹配的子字符串用于向后引用。這在使用或字符”(|)“來(lái)組合一個(gè)模式的各個(gè)部分是很有用。例如”industr(?:y|ies)“就是一個(gè)比”industry|industries“更簡(jiǎn)略的表達(dá)式。 (?=pattern) 正向肯定預(yù)查,在任何匹配 pattern 的字符串開(kāi)始處匹配查找字符串。這是一個(gè)非獲取匹配,也就是說(shuō),該匹配不需要獲取供以后使用。例如,”windows(?=95|98|NT|2000)“能匹配”Windows2000“中的”Windows“,但不能匹配” Windows3.1“中的”Windows“。預(yù)查不消耗字符,也就是說(shuō),在一個(gè)匹配發(fā)生后,在最后一次匹配之后立即開(kāi)始下一次匹配的搜索,而不是從包含預(yù)查的字符之后開(kāi)始。 (?!pattern) 正向否定預(yù)查,在任何不匹配 pattern 的字符串開(kāi)始處匹配查找字符串。這是一個(gè)非獲取匹配,也就是說(shuō),該匹配不需要獲取供以后使用。例如”Windows(?!95|98|NT|2000)“能匹配”Windows3.1“中的”Windows“,但不能匹配” Windows2000“中的”Windows“。預(yù)查不消耗字符,也就是說(shuō),在一個(gè)匹配發(fā)生后,在最后一次匹配之后立即開(kāi)始下一次匹配的搜索,而不是從包含預(yù)查的字符之后開(kāi)始 (?<=pattern) 反向肯定預(yù)查,與正向肯定預(yù)查類似,只是方向相反。例如,”(?<=95|98|NT|2000)Windows“能匹配”2000Windows“中的”Windows“,但不能匹配”3.1Windows“中的” Windows“。 (?<!pattern) 反向否定預(yù)查,與正向否定預(yù)查類似,只是方向相反。例如”(?<!95|98|NT|2000)Windows“能匹配”3.1Windows“中的”Windows“,但不能匹配”2000Windows“中的” Windows“。 x|y 匹配 x 或 y。例如,”z|food“能匹配”z“或”food“。”(z|f)ood“則匹配”zood“或” food“。 [xyz] 字符集合(character class)。匹配所包含的任意一個(gè)字符。例如,”[abc]“可以匹配”plain“中的”a“。特殊字符僅有反斜線 \ 保持特殊含義,用于轉(zhuǎn)義字符。其它特殊字符如星號(hào)、加號(hào)、各種括號(hào)等均作為普通字符。脫字符 ^ 如果出現(xiàn)在首位則表示負(fù)值字符集合;如果出現(xiàn)在字符串中間就僅作為普通字符。連字符 - 如果出現(xiàn)在字符串中間表示字符范圍描述;如果如果出現(xiàn)在首位則僅作為普通字符。 [^xyz] 排除型(negate)字符集合。匹配未列出的任意字符。例如,”[^abc]“可以匹配”plain“中的”plin“。 [a-z] 字符范圍。匹配指定范圍內(nèi)的任意字符。例如,”[a-z]“可以匹配”a“到”z“范圍內(nèi)的任意小寫(xiě)字母字符。 [^a-z] 排除型的字符范圍。匹配任何不在指定范圍內(nèi)的任意字符。例如,”[^a-z]“可以匹配任何不在”a“到”z“范圍內(nèi)的任意字符。 b 匹配一個(gè)單詞邊界,也就是指單詞和空格間的位置。例如,”erb“可以匹配”never“中的”er“,但不能匹配”verb“中的”er“。 B 匹配非單詞邊界。”erB“能匹配”verb“中的”er“,但不能匹配”never“中的”er“。 cx 匹配由 x 指明的控制字符。例如,cM 匹配一個(gè) Control-M 或回車符。x 的值必須為 A-Z 或 a-z 之一。否則,將 c 視為一個(gè)原義的”c“字符。 d 匹配一個(gè)數(shù)字字符。等價(jià)于 [0-9]。 D 匹配一個(gè)非數(shù)字字符。等價(jià)于 [^0-9]。 f 匹配一個(gè)換頁(yè)符。等價(jià)于 \x0c 和 cL。 n 匹配一個(gè)換行符。等價(jià)于 \x0a 和 cJ。 r 匹配一個(gè)回車符。等價(jià)于 \x0d 和 cM。 s 匹配任何空白字符,包括空格、制表符、換頁(yè)符等等。等價(jià)于 [ fnrtv]。 S 匹配任何非空白字符。等價(jià)于 [^ fnrtv]。 t 匹配一個(gè)制表符。等價(jià)于 \x09 和 cI。 v 匹配一個(gè)垂直制表符。等價(jià)于 \x0b 和 cK。 w 匹配包括下劃線的任何單詞字符。等價(jià)于”[A-Za-z0-9]“。 W 匹配任何非單詞字符。等價(jià)于”[^A-Za-z0-9]“。 \xn 匹配 n,其中 n 為十六進(jìn)制轉(zhuǎn)義值。十六進(jìn)制轉(zhuǎn)義值必須為確定的兩個(gè)數(shù)字長(zhǎng)。例如,”\x41“匹配”A“。”\x041“則等價(jià)于”\x04&1“。正則表達(dá)式中可以使用 ASCII 編碼。. num 向后引用(back-reference)一個(gè)子字符串(substring),該子字符串與正則表達(dá)式的第 num 個(gè)用括號(hào)圍起來(lái)的子表達(dá)式(subexpression)匹配。其中 num 是從 1 開(kāi)始的正整數(shù),其上限可能是 99。例如:”(.)1“匹配兩個(gè)連續(xù)的相同字符。 n 標(biāo)識(shí)一個(gè)八進(jìn)制轉(zhuǎn)義值或一個(gè)向后引用。如果 n 之前至少 n 個(gè)獲取的子表達(dá)式,則 n 為向后引用。否則,如果 n 為八進(jìn)制數(shù)字(0-7),則 n 為一個(gè)八進(jìn)制轉(zhuǎn)義值。 nm 標(biāo)識(shí)一個(gè)八進(jìn)制轉(zhuǎn)義值或一個(gè)向后引用。如果 nm 之前至少有 nm 個(gè)獲得子表達(dá)式,則 nm 為向后引用。如果 nm 之前至少有 n 個(gè)獲取,則 n 為一個(gè)后跟文字 m 的向后引用。如果前面的條件都不滿足,若 n 和 m 均為八進(jìn)制數(shù)字(0-7),則 nm 將匹配八進(jìn)制轉(zhuǎn)義值 nm。 nml 如果 n 為八進(jìn)制數(shù)字(0-3),且 m 和 l 均為八進(jìn)制數(shù)字(0-7),則匹配八進(jìn)制轉(zhuǎn)義值 nml。 \un 匹配 n,其中 n 是一個(gè)用四個(gè)十六進(jìn)制數(shù)字表示的 Unicode 字符。例如,\u00A9 匹配版權(quán)符號(hào)(©)。
參考文章
Wikipedia 正則表達(dá)式
MDN RegExp
正則表達(dá)式 30 分鐘入門(mén)教程 - DeerChao