這是一篇翻譯文章。我學(xué)過(guò)很多次正則表達(dá)式,總是學(xué)了忘,忘了學(xué),一到用的時(shí)候還是只能靠搜索引擎。
這回看到這個(gè)正則教程,感覺(jué)非常驚喜。嘗試翻譯了一遍,譯得不好,大家可以看原文,很容易理解。
原文地址:https://refrf.shreyasminocha.me/
1 介紹
正則表達(dá)式允許定義一種模式,并通過(guò)這種模式針對(duì)字符串執(zhí)行對(duì)應(yīng)的操作。與模式匹配的子字符串稱(chēng)為“匹配”。
正則表達(dá)式是定義搜索模式的一串字符。
正則表達(dá)式主要用在如下場(chǎng)景:
- 輸入驗(yàn)證
- 查找替換操作
- 高級(jí)字符串操作
- 文件搜索或重命名
- 白名單和黑名單
正則表達(dá)式不太適合用在這些場(chǎng)景:
有許多實(shí)現(xiàn)正則匹配的引擎,每種都有自己的特性。這本書(shū)將避免討論(不同引擎之間的)特性差異,而是只討論在大多數(shù)情況下不同引擎都共有的特征。
整本書(shū)中的示例使用JavaScript。因此,這本書(shū)可能會(huì)稍微偏向 JavaScript 的正則引擎。
2 基礎(chǔ)
正則表達(dá)式通常格式化為 /rules>/flags>
,通常為了簡(jiǎn)潔而省略后面的 /flags>
。關(guān)于 flag 我們將在下一章詳細(xì)討論。
讓我們從/p/g
這個(gè)正則表達(dá)式開(kāi)始?,F(xiàn)在,請(qǐng)將 /g
flag 視為固定不變的。
如我們所見(jiàn),/p/g
匹配所有小寫(xiě)的 p 字符。
注意
默認(rèn)情況下,正則表達(dá)式區(qū)分大小寫(xiě)。
在輸入字符串中找到的正則表達(dá)式模式的實(shí)例稱(chēng)為“匹配”。
3 字符組
可以從一組字符中匹配一個(gè)字符。
[aeiou]/g
匹配輸入字符串中的所有元音。
下面是另一個(gè)例子:
我們匹配一個(gè) p,后跟一個(gè)元音,然后是一個(gè) t。
有一個(gè)更直觀的快捷方式,可以在一個(gè)連續(xù)的范圍內(nèi)匹配一個(gè)字符。
警告
表達(dá)式 /[a-z]/g
只匹配一個(gè)字符。在上面的示例中,每個(gè)字符都有一個(gè)單獨(dú)的匹配項(xiàng)。不是整個(gè)字符串匹配。
我們也可以在正則表達(dá)式中組合范圍和單個(gè)字符。
我們的正則表達(dá)式 /[A-Za-z0-9_-]/g
匹配一個(gè)字符,該字符必須(至少)是以下字符之一:
我們也可以“否定”這些規(guī)則:
/[aeiou]/g
與 /[^aeiou]/g
之間的唯一區(qū)別是 ^ 緊跟在左括號(hào)之后。其目的是"否定"括號(hào)中定義的規(guī)則。它表示的意思是:
匹配任何不屬于a、e、i、o和 u 的字符
3.1 例子
非法的用戶名字符
指定字符
/[A-HJ-NP-Za-kmnp-z2-9]/g
4 字符轉(zhuǎn)義
字符轉(zhuǎn)義是對(duì)某些通用字符類(lèi)的簡(jiǎn)略表達(dá)方式。
4.1 數(shù)字字符 \d
轉(zhuǎn)義符 \d
表示匹配數(shù)字字符 0-9。等同于 [0-9]
。
\D
是\d
的反面,相當(dāng)于[^0-9]
。
4.2 單詞字符 \w
轉(zhuǎn)義符 \w
匹配單詞字符。包括:
- 小寫(xiě)字母 a-z
- 大寫(xiě)字母 A-Z
- 數(shù)字 0-9
- 下劃線 _
等價(jià)于 [a-zA-Z0-9_]
4.3 空白字符 \s
轉(zhuǎn)義符 \s
匹配空白字符。具體匹配的字符集取決于正則表達(dá)式引擎,但大多數(shù)至少包括:
- 空格
- tab 制表符
\t
- 回車(chē)
\r
- 換行符
\n
- 換頁(yè)
\f
其他還可能包括垂直制表符(\v)。Unicode自識(shí)別引擎通常匹配分隔符類(lèi)別中的所有字符。然而,技術(shù)細(xì)節(jié)通常并不重要。
4.4 任意字符 .
雖然不是典型的字符轉(zhuǎn)義。.
可以匹配任意1個(gè)字符。(除換行符 \n 以外,通過(guò) dotall 修飾符,也可以匹配換行符 \n)
5 轉(zhuǎn)義
在正則表達(dá)式中,有些字符有特殊的含義,我們將在這一章中進(jìn)行探討:
|
{,}
(,)
[,]
^
, $
+
, *
, ?
\
.
只在字符類(lèi)中的字面量。
-
: 有時(shí)是字符類(lèi)中的特殊字符。
當(dāng)我們想通過(guò)字面意思匹配這些字符時(shí),我們可以再這些字符前面加 \
“轉(zhuǎn)義”它們。
5.1 例子
JavaScript 內(nèi)聯(lián)注釋
星號(hào)包圍的子串
第一個(gè)和最后一個(gè)星號(hào)是字面上的,所有他們要用 \*
轉(zhuǎn)義。字符集里面的星號(hào)不需要被轉(zhuǎn)義,但為了清楚起見(jiàn),我還是轉(zhuǎn)義了它。緊跟在字符集后面的星號(hào)表示字符集的重復(fù),我們將在后面的章節(jié)中對(duì)此進(jìn)行探討。
6 組
顧名思義,組是用來(lái)“組合”正則表達(dá)式的組件的。這些組可用于:
- 提取匹配的子集
- 重復(fù)分組任意次數(shù)
- 參考先前匹配的子字符串
- 增強(qiáng)可讀性
- 允許復(fù)雜的替換
這一章我們先學(xué)組如何工作,之后的章節(jié)還會(huì)有更多例子。
6.1 捕獲組
捕獲組用(…)表示。下面是一個(gè)解釋性的例子:
捕獲組允許提取部分匹配項(xiàng)。
通過(guò)語(yǔ)言的正則函數(shù),您將能夠提取括號(hào)之間匹配的文本。
捕獲組還可以用于對(duì)正則表達(dá)式進(jìn)行部分分組,以便于重復(fù)。雖然我們將在接下來(lái)的章節(jié)中詳細(xì)介紹重復(fù),但這里有一個(gè)示例演示了組的實(shí)用性。
其他時(shí)候,它們用于對(duì)正則表達(dá)式的邏輯相似部分進(jìn)行分組,以提高可讀性。
6.2 回溯
回溯允許引用之前捕獲的子字符串。
匹配第一組可以使用 \1
,匹配第二組可以使用 \2
,依此類(lèi)推…
不能使用回溯來(lái)減少正則表達(dá)式中的重復(fù)。它們指的是組的匹配,而不是模式。
下面是一個(gè)演示常見(jiàn)用例的示例:
這不能通過(guò)重復(fù)的字符類(lèi)來(lái)實(shí)現(xiàn)。
6.3 非捕獲組
非捕獲組與捕獲組非常相似,只是它們不創(chuàng)建“捕獲”。而是采取形式 (?: ...)
非捕獲組通常與捕獲組一起使用。也許您正在嘗試使用捕獲組提取匹配的某些部分。而你可能希望使用一個(gè)組而不擾亂捕獲順序,這時(shí)候你應(yīng)該使用非捕獲組。
6.4 例子
查詢(xún)字符串參數(shù)
/^\&;(\w+)=(\w+)(?:(\w+)=(\w+))*$/g
我們單獨(dú)匹配第一組鍵值對(duì),因?yàn)檫@可以讓我么使用 分隔符, 作為重復(fù)組的一部分。
(基礎(chǔ)的) HTML 標(biāo)簽
根據(jù)經(jīng)驗(yàn),不要使用正則表達(dá)式來(lái)匹配 XML/HTML。不過(guò),我還是提供相關(guān)的一個(gè)例子:
姓名
查找:\b(\w+) (\w+)\b
替換:
在替換操作,經(jīng)常使用 2;捕獲使用 \1
, \2
替換之前
John Doe
Jane Doe
Sven Svensson
Janez Novak
Janez Kranjski
Tim Joe
替換之后
Doe, John
Doe, Jane
Svensson, Sven
Novak, Janez
Kranjski, Janez
Joe, Tim
回溯和復(fù)數(shù)
查找: \bword(s?)\b
替換: phrase$1
替換之前
This is a paragraph with some words.
Some instances of the word "word" are in their plural form: "words".
替換之后
This is a paragraph with some phrases.
Yet, some are in their singular form: "phrase".
7 重復(fù)
重復(fù)是一個(gè)強(qiáng)大而普遍的正則表達(dá)式特性。在正則表達(dá)式中有幾種表示重復(fù)的方法。
7.1 可選項(xiàng)
我們可以使用 ?將某一部分設(shè)置成可選的(0或者1次)。
另一個(gè)例子:
我們還可以讓捕獲組和非捕獲組編程可選的。
/url: (www\.)?example\.com/g
7.2 零次或者多次
如果我們希望匹配零個(gè)或多個(gè)標(biāo)記,可以用 * 作為后綴。
我們的正則表達(dá)式甚至匹配一個(gè)空字符串。
7.3 一次或者多次
如果我們希望匹配 1 個(gè)或多個(gè)標(biāo)記,可以用 + 作為后綴。
7.4 精確的 x 次
如果我們希望匹配特定的標(biāo)記正好x次,我們可以添加{x}后綴。這在功能上等同于復(fù)制粘貼該標(biāo)記 x 次。
下面是匹配大寫(xiě)的六個(gè)字符的十六進(jìn)制顏色代碼的例子。
這里,標(biāo)記 {6} 應(yīng)用于字符集 [0-9A-F]。
7.5 最小次和最大次之間
如果我們希望在最小次和最大次之間匹配一個(gè)特定標(biāo)記,可以在這個(gè)標(biāo)記后添加 {min,max}
。
警告
{min,max}
中逗號(hào)后面不要有空格。
7.6 最少 x 次
如果我們希望匹配一個(gè)特定的標(biāo)記最少 x 次,可以在標(biāo)記后添加 {x,}。 和 {min, max} 類(lèi)似,只是沒(méi)有上限了。
7.7 貪婪模式的注意事項(xiàng)
正則表達(dá)式默認(rèn)使用貪婪模式。在貪婪模式下,會(huì)盡可能多的匹配符合要求的字符。
在**重復(fù)操作符(?,*,+,...)**后面添加 ?
,可以讓匹配變“懶”。
在這里,這也可以通過(guò)使用[^"]
代替。(這是最好的做法)。
懶惰,意味著只要條件滿足,就立即停止;但貪婪意味著只有條件不再滿足才停止。
-Andrew S on StackOverflow
7.8 例子
比特幣地址
/([13][a-km-zA-HJ-NP-Z0-9]{26,33})/g
(思考: {26,33}?呢)
Youtube 視頻
/(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\&;.*?v=([^\s]+).*/gm
我們可以使用錨點(diǎn)調(diào)整表達(dá)式不讓它匹配最后一個(gè)不正確的鏈接,之后我們會(huì)接觸到。
8 交替
交替允許匹配幾個(gè)短語(yǔ)中的一個(gè)。這比僅限于單個(gè)字符的字符組更加強(qiáng)大。
使用管道符號(hào) |
把多個(gè)短語(yǔ)之間分開(kāi)
匹配 foo, bar, 和 baz 中的一個(gè)。
如果正則中只有一部分需要“交替”,可以使用組進(jìn)行包裹,捕獲組和非捕獲組都可以。
Try 后面跟著 foo, bar, 和 baz 中的一個(gè)。
匹配 100-250 中間的數(shù)字:
這個(gè)可以使用 Regex Numeric Range Generator 工具生成。
例子
十六進(jìn)制顏色
讓我們改進(jìn)一下之前十六進(jìn)制顏色匹配的例子。
/#[0-9A-F]{6}|[0-9A-F]{3}
[0-9A-F]{6}
要放在[0-9A-F]{3}
的前面,這一點(diǎn)非常重要。否則:
/#([0-9A-F]{3}|[0-9A-F]{6})/g
小提示
正則表達(dá)式引擎是從左邊到右邊的嘗試交替的。
羅馬數(shù)字
/^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/g
9 修飾符
修飾符允許我們把正則表達(dá)式分成不同的 "模式"。
修飾符是 /pattern/
后面的部分。
不同引擎支持不同的修飾符。在這里我們只討論最常見(jiàn)修飾符。
9.1 全局修飾符(g)
到現(xiàn)在為止,所有的例子都設(shè)置了全局修飾符。如果不啟用全局修飾符,正則表達(dá)式匹配第一個(gè)以后將不再匹配其他任何字符。
9.2 不區(qū)分大小寫(xiě)修飾符(i)
顧名思義,啟用這個(gè)修飾符會(huì)使正則在匹配時(shí)不區(qū)分大小寫(xiě)。
9.3 多行模式修飾符(m)
有限支持
在 Ruby 中,m 修飾符是執(zhí)行其他的函數(shù)。
多行修飾符與正在在處理包含換行符的“多行”字符串時(shí)對(duì)錨點(diǎn)的處理有關(guān)。默認(rèn)情況下,/^foo$/
只匹配 “foo”。
我們可能希望它在多行字符串中的一行也能匹配 foo。
我們拿 "bar\nfoo\nbaz"
舉例子:
bar foo baz
如果沒(méi)有 m 修飾符,上面的字符串會(huì)被當(dāng)做單行 bar\nfoo\nbaz
, 正則表達(dá)式 ^foo$
匹配不到任何字符。
如果有 m 修飾符,上面的字符串會(huì)被當(dāng)做 3 行。 ^foo$
可以匹配到中間那一行。
9.4 Dot-all修飾符 (s)
有限支持
ES2018 之前的 JavaScript 不支持這個(gè)修飾符。 Ruby 也不支持這個(gè)修飾,而是用 m 表示。
.
通常匹配除換行符以外的任何字符。使用dot all修飾符后,它也可以匹配換行符。
10 錨點(diǎn)
錨點(diǎn)本身不匹配任何東西。但是,他們會(huì)限制匹配出現(xiàn)的位置。
你可以把錨點(diǎn)當(dāng)做是 "不可見(jiàn)的字符"。
10.1 行首 ^
在正則開(kāi)始時(shí)插入^
號(hào),使正則其余部分必須從字符串開(kāi)始的地方匹配。你可以把它當(dāng)成始終要在字符串開(kāi)頭匹配一個(gè)不可見(jiàn)的字符。
10.2 行尾
在正則結(jié)尾時(shí)插入$
號(hào), 類(lèi)似于行首符。你可以把它當(dāng)成始終要在字符串結(jié)尾匹配一個(gè)不可見(jiàn)的字符。
^
和$
錨點(diǎn)經(jīng)常一起使用,以確保正則和字符串整個(gè)匹配,而不僅僅是部分匹配。
讓我們回顧一下重復(fù)中的一個(gè)例子,并在正則的末尾添加兩個(gè)錨點(diǎn)。
如果沒(méi)有這 2 個(gè)錨點(diǎn), http/2
和 shttp
也會(huì)被匹配。
10.3 字邊界 \b
字邊界是一個(gè)字符和非詞字符之間的位置。
字邊界錨點(diǎn) \b
,匹配字符和非詞字符之間存在的假想不可見(jiàn)字符。
提示
字符包括 a-z
, A-Z
, 0-9
, 和_
.
還有一個(gè)非字邊界錨 \B
。
顧名思義,它匹配除字邊界之外的所有內(nèi)容。
小提示
^…$
和\b…\b
是常見(jiàn)的模式,您幾乎總是需要這 2 個(gè)防止意外匹配。
10.4 例子
尾部空格
markdown 標(biāo)題
沒(méi)有錨點(diǎn):
11 零寬斷言(lookaround)
零寬斷言可用于驗(yàn)證條件,而不匹配任何文本。
你只能看,不能動(dòng)。
- 先行斷言(lookhead)
- 先行斷言(lookbehind)
11.1 先行斷言(lookhead)
正向(positive)
注意后面的字符是如何不匹配的。可以通過(guò)正面前看得到證實(shí)。
/(.+)_(?=[aeiou])(?=\1)/g
正則引擎在 _
使用了 (?=[aeiou])
和 (?=\1)
進(jìn)行檢查。
負(fù)向(Negative)
如果沒(méi)有錨點(diǎn),將匹配每個(gè)示例中沒(méi)有#的部分。
負(fù)向的先行斷言常常用于防止匹配特定短語(yǔ)。
11.2 例子
密碼驗(yàn)證
/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/
零寬斷言可用于驗(yàn)證多個(gè)條件。
帶引號(hào)的字符串
如果沒(méi)有先行斷言,我們最多只能做到這樣:
12 進(jìn)階例子
JavaScript 注釋
/\/\*[\s\S]*?\*\/|\/\/.*/g
[\s\S]
是一種匹配任何字符(包括換行符)的技巧。我們避免使用dot-all 修飾符,因?yàn)槲覀冃枰褂?code>. 表示單行注釋。
24小時(shí)時(shí)間
/^([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$/g
IP 地址
/\b(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\b/g
元標(biāo)簽
/Example source="(.*?)" flags="(.*?)">/gm
替換: Example regex={/$1/$2}>
浮點(diǎn)數(shù)
正向的先行斷言 (?=\.\d|\d)
確保不會(huì)匹配 ..
HSL顏色
從0到360的整數(shù)
/^0*(?:360|3[0-5]\d|[12]?\d?\d)$/g
百分比
/^(?:100(?:\.0+)?|\d?\d(?:\.\d+)?)%$/g
HSL 和 百分比
/^hsl\(\s*0*(?:360|3[0-5]\d|[12]?\d?\d)\s*(?:,\s*0*(?:100(?:\.0+)?|\d?\d(?:\.\d+)?)%\s*){2}\)$/gi
13 下一步
如果你像進(jìn)一步學(xué)習(xí)正則表達(dá)式及其工作原理:
- awesome-regex
regex
tag on StackOverflow
- StackOverflow RegEx FAQ
- r/regex
- RexEgg
- Regular-Expressions.info
- Regex Crossword
- Regex Golf
謝謝閱讀!添加微信:手邊字節(jié)