這是一篇翻譯文章。我學(xué)過很多次正則表達(dá)式,總是學(xué)了忘,忘了學(xué),一到用的時候還是只能靠搜索引擎。
這回看到這個正則教程,感覺非常驚喜。嘗試翻譯了一遍,譯得不好,大家可以看原文,很容易理解。
原文地址:https://refrf.shreyasminocha.me/
1 介紹
正則表達(dá)式允許定義一種模式,并通過這種模式針對字符串執(zhí)行對應(yīng)的操作。與模式匹配的子字符串稱為“匹配”。
正則表達(dá)式是定義搜索模式的一串字符。
正則表達(dá)式主要用在如下場景:
- 輸入驗證
- 查找替換操作
- 高級字符串操作
- 文件搜索或重命名
- 白名單和黑名單
正則表達(dá)式不太適合用在這些場景:
有許多實現(xiàn)正則匹配的引擎,每種都有自己的特性。這本書將避免討論(不同引擎之間的)特性差異,而是只討論在大多數(shù)情況下不同引擎都共有的特征。
整本書中的示例使用JavaScript。因此,這本書可能會稍微偏向 JavaScript 的正則引擎。
2 基礎(chǔ)
正則表達(dá)式通常格式化為 /rules>/flags>
,通常為了簡潔而省略后面的 /flags>
。關(guān)于 flag 我們將在下一章詳細(xì)討論。
讓我們從/p/g
這個正則表達(dá)式開始?,F(xiàn)在,請將 /g
flag 視為固定不變的。

如我們所見,/p/g
匹配所有小寫的 p 字符。
注意
默認(rèn)情況下,正則表達(dá)式區(qū)分大小寫。
在輸入字符串中找到的正則表達(dá)式模式的實例稱為“匹配”。

3 字符組
可以從一組字符中匹配一個字符。

[aeiou]/g
匹配輸入字符串中的所有元音。
下面是另一個例子:

我們匹配一個 p,后跟一個元音,然后是一個 t。
有一個更直觀的快捷方式,可以在一個連續(xù)的范圍內(nèi)匹配一個字符。

警告
表達(dá)式 /[a-z]/g
只匹配一個字符。在上面的示例中,每個字符都有一個單獨的匹配項。不是整個字符串匹配。
我們也可以在正則表達(dá)式中組合范圍和單個字符。

我們的正則表達(dá)式 /[A-Za-z0-9_-]/g
匹配一個字符,該字符必須(至少)是以下字符之一:
我們也可以“否定”這些規(guī)則:

/[aeiou]/g
與 /[^aeiou]/g
之間的唯一區(qū)別是 ^ 緊跟在左括號之后。其目的是"否定"括號中定義的規(guī)則。它表示的意思是:
匹配任何不屬于a、e、i、o和 u 的字符
3.1 例子
非法的用戶名字符

指定字符
/[A-HJ-NP-Za-kmnp-z2-9]/g

4 字符轉(zhuǎn)義
字符轉(zhuǎ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
匹配單詞字符。包括:
- 小寫字母 a-z
- 大寫字母 A-Z
- 數(shù)字 0-9
- 下劃線 _
等價于 [a-zA-Z0-9_]


4.3 空白字符 \s
轉(zhuǎn)義符 \s
匹配空白字符。具體匹配的字符集取決于正則表達(dá)式引擎,但大多數(shù)至少包括:
- 空格
- tab 制表符
\t
- 回車
\r
- 換行符
\n
- 換頁
\f
其他還可能包括垂直制表符(\v)。Unicode自識別引擎通常匹配分隔符類別中的所有字符。然而,技術(shù)細(xì)節(jié)通常并不重要。


4.4 任意字符 .
雖然不是典型的字符轉(zhuǎn)義。.
可以匹配任意1個字符。(除換行符 \n 以外,通過 dotall 修飾符,也可以匹配換行符 \n)

5 轉(zhuǎn)義
在正則表達(dá)式中,有些字符有特殊的含義,我們將在這一章中進行探討:
|
{,}
(,)
[,]
^
, $
+
, *
, ?
\
.
只在字符類中的字面量。
-
: 有時是字符類中的特殊字符。
當(dāng)我們想通過字面意思匹配這些字符時,我們可以再這些字符前面加 \
“轉(zhuǎn)義”它們。








5.1 例子
JavaScript 內(nèi)聯(lián)注釋

星號包圍的子串

第一個和最后一個星號是字面上的,所有他們要用 \*
轉(zhuǎn)義。字符集里面的星號不需要被轉(zhuǎn)義,但為了清楚起見,我還是轉(zhuǎn)義了它。緊跟在字符集后面的星號表示字符集的重復(fù),我們將在后面的章節(jié)中對此進行探討。
6 組
顧名思義,組是用來“組合”正則表達(dá)式的組件的。這些組可用于:
- 提取匹配的子集
- 重復(fù)分組任意次數(shù)
- 參考先前匹配的子字符串
- 增強可讀性
- 允許復(fù)雜的替換
這一章我們先學(xué)組如何工作,之后的章節(jié)還會有更多例子。
6.1 捕獲組
捕獲組用(…)表示。下面是一個解釋性的例子:

捕獲組允許提取部分匹配項。

通過語言的正則函數(shù),您將能夠提取括號之間匹配的文本。
捕獲組還可以用于對正則表達(dá)式進行部分分組,以便于重復(fù)。雖然我們將在接下來的章節(jié)中詳細(xì)介紹重復(fù),但這里有一個示例演示了組的實用性。

其他時候,它們用于對正則表達(dá)式的邏輯相似部分進行分組,以提高可讀性。

6.2 回溯
回溯允許引用之前捕獲的子字符串。
匹配第一組可以使用 \1
,匹配第二組可以使用 \2
,依此類推…

不能使用回溯來減少正則表達(dá)式中的重復(fù)。它們指的是組的匹配,而不是模式。


下面是一個演示常見用例的示例:

這不能通過重復(fù)的字符類來實現(xiàn)。

6.3 非捕獲組
非捕獲組與捕獲組非常相似,只是它們不創(chuàng)建“捕獲”。而是采取形式 (?: ...)
非捕獲組通常與捕獲組一起使用。也許您正在嘗試使用捕獲組提取匹配的某些部分。而你可能希望使用一個組而不擾亂捕獲順序,這時候你應(yīng)該使用非捕獲組。
6.4 例子
查詢字符串參數(shù)
/^\&;(\w+)=(\w+)(?:(\w+)=(\w+))*$/g

我們單獨匹配第一組鍵值對,因為這可以讓我么使用 分隔符, 作為重復(fù)組的一部分。
(基礎(chǔ)的) HTML 標(biāo)簽
根據(jù)經(jīng)驗,不要使用正則表達(dá)式來匹配 XML/HTML。不過,我還是提供相關(guān)的一個例子:

姓名
查找:\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ù)是一個強大而普遍的正則表達(dá)式特性。在正則表達(dá)式中有幾種表示重復(fù)的方法。
7.1 可選項
我們可以使用 ?將某一部分設(shè)置成可選的(0或者1次)。

另一個例子:

我們還可以讓捕獲組和非捕獲組編程可選的。
/url: (www\.)?example\.com/g

7.2 零次或者多次
如果我們希望匹配零個或多個標(biāo)記,可以用 * 作為后綴。

我們的正則表達(dá)式甚至匹配一個空字符串。
7.3 一次或者多次
如果我們希望匹配 1 個或多個標(biāo)記,可以用 + 作為后綴。

7.4 精確的 x 次
如果我們希望匹配特定的標(biāo)記正好x次,我們可以添加{x}后綴。這在功能上等同于復(fù)制粘貼該標(biāo)記 x 次。

下面是匹配大寫的六個字符的十六進制顏色代碼的例子。

這里,標(biāo)記 {6} 應(yīng)用于字符集 [0-9A-F]。
7.5 最小次和最大次之間
如果我們希望在最小次和最大次之間匹配一個特定標(biāo)記,可以在這個標(biāo)記后添加 {min,max}
。

警告
{min,max}
中逗號后面不要有空格。
7.6 最少 x 次
如果我們希望匹配一個特定的標(biāo)記最少 x 次,可以在標(biāo)記后添加 {x,}。 和 {min, max} 類似,只是沒有上限了。

7.7 貪婪模式的注意事項
正則表達(dá)式默認(rèn)使用貪婪模式。在貪婪模式下,會盡可能多的匹配符合要求的字符。


在**重復(fù)操作符(?,*,+,...)**后面添加 ?
,可以讓匹配變“懶”。

在這里,這也可以通過使用[^"]
代替。(這是最好的做法)。

懶惰,意味著只要條件滿足,就立即停止;但貪婪意味著只有條件不再滿足才停止。
-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ào)整表達(dá)式不讓它匹配最后一個不正確的鏈接,之后我們會接觸到。
8 交替
交替允許匹配幾個短語中的一個。這比僅限于單個字符的字符組更加強大。
使用管道符號 |
把多個短語之間分開

匹配 foo, bar, 和 baz 中的一個。
如果正則中只有一部分需要“交替”,可以使用組進行包裹,捕獲組和非捕獲組都可以。

Try 后面跟著 foo, bar, 和 baz 中的一個。
匹配 100-250 中間的數(shù)字:

這個可以使用 Regex Numeric Range Generator 工具生成。
例子
十六進制顏色
讓我們改進一下之前十六進制顏色匹配的例子。
/#[0-9A-F]{6}|[0-9A-F]{3}

[0-9A-F]{6}
要放在[0-9A-F]{3}
的前面,這一點非常重要。否則:
/#([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/
后面的部分。
不同引擎支持不同的修飾符。在這里我們只討論最常見修飾符。
9.1 全局修飾符(g)
到現(xiàn)在為止,所有的例子都設(shè)置了全局修飾符。如果不啟用全局修飾符,正則表達(dá)式匹配第一個以后將不再匹配其他任何字符。


9.2 不區(qū)分大小寫修飾符(i)
顧名思義,啟用這個修飾符會使正則在匹配時不區(qū)分大小寫。



9.3 多行模式修飾符(m)
有限支持
在 Ruby 中,m 修飾符是執(zhí)行其他的函數(shù)。
多行修飾符與正在在處理包含換行符的“多行”字符串時對錨點的處理有關(guān)。默認(rèn)情況下,/^foo$/
只匹配 “foo”。
我們可能希望它在多行字符串中的一行也能匹配 foo。
我們拿 "bar\nfoo\nbaz"
舉例子:
bar foo baz
如果沒有 m 修飾符,上面的字符串會被當(dāng)做單行 bar\nfoo\nbaz
, 正則表達(dá)式 ^foo$
匹配不到任何字符。
如果有 m 修飾符,上面的字符串會被當(dāng)做 3 行。 ^foo$
可以匹配到中間那一行。
9.4 Dot-all修飾符 (s)
有限支持
ES2018 之前的 JavaScript 不支持這個修飾符。 Ruby 也不支持這個修飾,而是用 m 表示。
.
通常匹配除換行符以外的任何字符。使用dot all修飾符后,它也可以匹配換行符。
10 錨點
錨點本身不匹配任何東西。但是,他們會限制匹配出現(xiàn)的位置。
你可以把錨點當(dāng)做是 "不可見的字符"。
10.1 行首 ^
在正則開始時插入^
號,使正則其余部分必須從字符串開始的地方匹配。你可以把它當(dāng)成始終要在字符串開頭匹配一個不可見的字符。

10.2 行尾
在正則結(jié)尾時插入$
號, 類似于行首符。你可以把它當(dāng)成始終要在字符串結(jié)尾匹配一個不可見的字符。

^
和$
錨點經(jīng)常一起使用,以確保正則和字符串整個匹配,而不僅僅是部分匹配。

讓我們回顧一下重復(fù)中的一個例子,并在正則的末尾添加兩個錨點。

如果沒有這 2 個錨點, http/2
和 shttp
也會被匹配。
10.3 字邊界 \b
字邊界是一個字符和非詞字符之間的位置。
字邊界錨點 \b
,匹配字符和非詞字符之間存在的假想不可見字符。

提示
字符包括 a-z
, A-Z
, 0-9
, 和_
.


還有一個非字邊界錨 \B
。
顧名思義,它匹配除字邊界之外的所有內(nèi)容。


小提示
^…$
和\b…\b
是常見的模式,您幾乎總是需要這 2 個防止意外匹配。
10.4 例子
尾部空格

markdown 標(biāo)題

沒有錨點:

11 零寬斷言(lookaround)
零寬斷言可用于驗證條件,而不匹配任何文本。
你只能看,不能動。
- 先行斷言(lookhead)
- 先行斷言(lookbehind)
11.1 先行斷言(lookhead)
正向(positive)

注意后面的字符是如何不匹配的??梢酝ㄟ^正面前看得到證實。
/(.+)_(?=[aeiou])(?=\1)/g

正則引擎在 _
使用了 (?=[aeiou])
和 (?=\1)
進行檢查。

負(fù)向(Negative)


如果沒有錨點,將匹配每個示例中沒有#的部分。
負(fù)向的先行斷言常常用于防止匹配特定短語。


11.2 例子
密碼驗證
/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/

零寬斷言可用于驗證多個條件。
帶引號的字符串

如果沒有先行斷言,我們最多只能做到這樣:

12 進階例子
JavaScript 注釋
/\/\*[\s\S]*?\*\/|\/\/.*/g

[\s\S]
是一種匹配任何字符(包括換行符)的技巧。我們避免使用dot-all 修飾符,因為我們需要使用.
表示單行注釋。
24小時時間
/^([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}>
浮點數(shù)

正向的先行斷言 (?=\.\d|\d)
確保不會匹配 ..
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 下一步
如果你像進一步學(xué)習(xí)正則表達(dá)式及其工作原理:
- awesome-regex
regex
tag on StackOverflow
- StackOverflow RegEx FAQ
- r/regex
- RexEgg
- Regular-Expressions.info
- Regex Crossword
- Regex Golf
謝謝閱讀!添加微信:手邊字節(jié)