這是一篇翻譯文章。我學(xué)過很多次正則表達式,總是學(xué)了忘,忘了學(xué),一到用的時候還是只能靠搜索引擎。
這回看到這個正則教程,感覺非常驚喜。嘗試翻譯了一遍,譯得不好,大家可以看原文,很容易理解。
原文地址:https://refrf.shreyasminocha.me/
1 介紹
正則表達式允許定義一種模式,并通過這種模式針對字符串執(zhí)行對應(yīng)的操作。與模式匹配的子字符串稱為“匹配”。
正則表達式是定義搜索模式的一串字符。
正則表達式主要用在如下場景:
- 輸入驗證
- 查找替換操作
- 高級字符串操作
- 文件搜索或重命名
- 白名單和黑名單
正則表達式不太適合用在這些場景:
有許多實現(xiàn)正則匹配的引擎,每種都有自己的特性。這本書將避免討論(不同引擎之間的)特性差異,而是只討論在大多數(shù)情況下不同引擎都共有的特征。
整本書中的示例使用JavaScript。因此,這本書可能會稍微偏向 JavaScript 的正則引擎。
2 基礎(chǔ)
正則表達式通常格式化為 /rules>/flags>
,通常為了簡潔而省略后面的 /flags>
。關(guān)于 flag 我們將在下一章詳細討論。
讓我們從/p/g
這個正則表達式開始?,F(xiàn)在,請將 /g
flag 視為固定不變的。
![](/d/20211017/429fea22ba599c3af4c7ecea2084bca7.gif)
如我們所見,/p/g
匹配所有小寫的 p 字符。
注意
默認情況下,正則表達式區(qū)分大小寫。
在輸入字符串中找到的正則表達式模式的實例稱為“匹配”。
![](/d/20211017/5ef37391730e7f13c3837b6c4664efdf.gif)
3 字符組
可以從一組字符中匹配一個字符。
![](/d/20211017/fd23c6e0bbfb676cbd299a3e52732e15.gif)
[aeiou]/g
匹配輸入字符串中的所有元音。
下面是另一個例子:
![](/d/20211017/5c4d1528b4550c3fd107fdd5b42bb41f.gif)
我們匹配一個 p,后跟一個元音,然后是一個 t。
有一個更直觀的快捷方式,可以在一個連續(xù)的范圍內(nèi)匹配一個字符。
![](/d/20211017/aa386fb6cbad8242539cbe3a8fc8396d.gif)
警告
表達式 /[a-z]/g
只匹配一個字符。在上面的示例中,每個字符都有一個單獨的匹配項。不是整個字符串匹配。
我們也可以在正則表達式中組合范圍和單個字符。
![](/d/20211017/7e563d81e2ac26ef759669d365852ba8.gif)
我們的正則表達式 /[A-Za-z0-9_-]/g
匹配一個字符,該字符必須(至少)是以下字符之一:
我們也可以“否定”這些規(guī)則:
![](/d/20211017/55164bc3c6afbe1b6e4bc25a68996fbf.gif)
/[aeiou]/g
與 /[^aeiou]/g
之間的唯一區(qū)別是 ^ 緊跟在左括號之后。其目的是"否定"括號中定義的規(guī)則。它表示的意思是:
匹配任何不屬于a、e、i、o和 u 的字符
3.1 例子
非法的用戶名字符
![](/d/20211017/a1f651d4e3c3ea7cb4e89e15d133edad.gif)
指定字符
/[A-HJ-NP-Za-kmnp-z2-9]/g
![](/d/20211017/88a875f653cc28042eddd157518d6ff5.gif)
4 字符轉(zhuǎn)義
字符轉(zhuǎn)義是對某些通用字符類的簡略表達方式。
4.1 數(shù)字字符 \d
轉(zhuǎn)義符 \d
表示匹配數(shù)字字符 0-9。等同于 [0-9]
。
![](/d/20211017/2805a6466a2cdfb8c2fe5021ccd330b2.gif)
![](/d/20211017/6176ad064a0907ed7a031ea2b5e26074.gif)
\D
是\d
的反面,相當(dāng)于[^0-9]
。
![](/d/20211017/d7e59edc880e1854f1c61802c23b613c.gif)
4.2 單詞字符 \w
轉(zhuǎn)義符 \w
匹配單詞字符。包括:
- 小寫字母 a-z
- 大寫字母 A-Z
- 數(shù)字 0-9
- 下劃線 _
等價于 [a-zA-Z0-9_]
![](/d/20211017/bc99c8080a4d53e38d2e14d996ce4b2a.gif)
![](/d/20211017/e4bb087e1f24f471eeeb63b6cbae6d7a.gif)
4.3 空白字符 \s
轉(zhuǎn)義符 \s
匹配空白字符。具體匹配的字符集取決于正則表達式引擎,但大多數(shù)至少包括:
- 空格
- tab 制表符
\t
- 回車
\r
- 換行符
\n
- 換頁
\f
其他還可能包括垂直制表符(\v)。Unicode自識別引擎通常匹配分隔符類別中的所有字符。然而,技術(shù)細節(jié)通常并不重要。
![](/d/20211017/0a8daad56fab5db6dbf37e6ec29b7b3a.gif)
![](/d/20211017/f7ffe908de58c4288aea8d4f79b1b8d3.gif)
4.4 任意字符 .
雖然不是典型的字符轉(zhuǎn)義。.
可以匹配任意1個字符。(除換行符 \n 以外,通過 dotall 修飾符,也可以匹配換行符 \n)
![](/d/20211017/f1a1b1a04184d5e47dbf8d9b8da97749.gif)
5 轉(zhuǎn)義
在正則表達式中,有些字符有特殊的含義,我們將在這一章中進行探討:
|
{,}
(,)
[,]
^
, $
+
, *
, ?
\
.
只在字符類中的字面量。
-
: 有時是字符類中的特殊字符。
當(dāng)我們想通過字面意思匹配這些字符時,我們可以再這些字符前面加 \
“轉(zhuǎn)義”它們。
![](/d/20211017/8ccd4ed580701950788ad7d8ed825c8b.gif)
![](/d/20211017/f1a28ee0a5f0266d2620c0f0ba378b67.gif)
![](/d/20211017/14e7fe6defd9756aca79de2606acfbf8.gif)
![](/d/20211017/b426471117975affb9bf538e4edf7be7.gif)
![](/d/20211017/f37131212ceba5f2242c5a03c0b549ef.gif)
![](/d/20211017/d078444b80a41fcd779d4b1136b2d51d.gif)
![](/d/20211017/fafc732f4474562d195b61018d6652d4.gif)
![](/d/20211017/ebabc93ba919425e2a9f29ef32433749.gif)
5.1 例子
JavaScript 內(nèi)聯(lián)注釋
![](/d/20211017/0014134f329786f4d3ae3f6c7d4b1a2b.gif)
星號包圍的子串
![](/d/20211017/db1f3a86611c7627d43c26bfb3862b1e.gif)
第一個和最后一個星號是字面上的,所有他們要用 \*
轉(zhuǎn)義。字符集里面的星號不需要被轉(zhuǎn)義,但為了清楚起見,我還是轉(zhuǎn)義了它。緊跟在字符集后面的星號表示字符集的重復(fù),我們將在后面的章節(jié)中對此進行探討。
6 組
顧名思義,組是用來“組合”正則表達式的組件的。這些組可用于:
- 提取匹配的子集
- 重復(fù)分組任意次數(shù)
- 參考先前匹配的子字符串
- 增強可讀性
- 允許復(fù)雜的替換
這一章我們先學(xué)組如何工作,之后的章節(jié)還會有更多例子。
6.1 捕獲組
捕獲組用(…)表示。下面是一個解釋性的例子:
![](/d/20211017/b377356497e28e4b3253d9bae294f01c.gif)
捕獲組允許提取部分匹配項。
![](/d/20211017/0d9ea2a1c7fabf1c6aeaaedac220eeba.gif)
通過語言的正則函數(shù),您將能夠提取括號之間匹配的文本。
捕獲組還可以用于對正則表達式進行部分分組,以便于重復(fù)。雖然我們將在接下來的章節(jié)中詳細介紹重復(fù),但這里有一個示例演示了組的實用性。
![](/d/20211017/5b849c97e26d83fc8a34ec6072d4e226.gif)
其他時候,它們用于對正則表達式的邏輯相似部分進行分組,以提高可讀性。
![](/d/20211017/8cbe7f7f9d040ac6fffc411470cda2e6.gif)
6.2 回溯
回溯允許引用之前捕獲的子字符串。
匹配第一組可以使用 \1
,匹配第二組可以使用 \2
,依此類推…
![](/d/20211017/49361a0d8aaa1451bdb061c5d6e613de.gif)
不能使用回溯來減少正則表達式中的重復(fù)。它們指的是組的匹配,而不是模式。
![](/d/20211017/56853db69a5740101d23463e5ec61bd6.gif)
![](/d/20211017/aa5935216fd9a85838e0e2cbc167d85d.gif)
下面是一個演示常見用例的示例:
![](/d/20211017/ba2a89c227ebc8c4f44c7174578149c6.gif)
這不能通過重復(fù)的字符類來實現(xiàn)。
![](/d/20211017/94ba68202e5ac1da090c962f03815fe6.gif)
6.3 非捕獲組
非捕獲組與捕獲組非常相似,只是它們不創(chuàng)建“捕獲”。而是采取形式 (?: ...)
非捕獲組通常與捕獲組一起使用。也許您正在嘗試使用捕獲組提取匹配的某些部分。而你可能希望使用一個組而不擾亂捕獲順序,這時候你應(yīng)該使用非捕獲組。
6.4 例子
查詢字符串參數(shù)
/^\&;(\w+)=(\w+)(?:(\w+)=(\w+))*$/g
![](/d/20211017/858a81a7bc2d431c6a1dddbffcab9c72.gif)
我們單獨匹配第一組鍵值對,因為這可以讓我么使用 分隔符, 作為重復(fù)組的一部分。
(基礎(chǔ)的) HTML 標簽
根據(jù)經(jīng)驗,不要使用正則表達式來匹配 XML/HTML。不過,我還是提供相關(guān)的一個例子:
![](/d/20211017/29f586f6a9a474cfb922167ef5ba714d.gif)
姓名
查找:\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ù)是一個強大而普遍的正則表達式特性。在正則表達式中有幾種表示重復(fù)的方法。
7.1 可選項
我們可以使用 ?將某一部分設(shè)置成可選的(0或者1次)。
![](/d/20211017/9d0ffcfd27dd951b830e8b1932c00380.gif)
另一個例子:
![](/d/20211017/9b2fcba5d4b064e47c27a9bd26ce27f9.gif)
我們還可以讓捕獲組和非捕獲組編程可選的。
/url: (www\.)?example\.com/g
![](/d/20211017/68ce6a50ae1cb194e992474da24da34f.gif)
7.2 零次或者多次
如果我們希望匹配零個或多個標記,可以用 * 作為后綴。
![](/d/20211017/43b1e8be612ce2b6f2ac132834365012.gif)
我們的正則表達式甚至匹配一個空字符串。
7.3 一次或者多次
如果我們希望匹配 1 個或多個標記,可以用 + 作為后綴。
![](/d/20211017/e3525fac28043872db8d800170c013ef.gif)
7.4 精確的 x 次
如果我們希望匹配特定的標記正好x次,我們可以添加{x}后綴。這在功能上等同于復(fù)制粘貼該標記 x 次。
![](/d/20211017/3c9082de9b79525010d2382cdcfd8322.gif)
下面是匹配大寫的六個字符的十六進制顏色代碼的例子。
![](/d/20211017/59b11d3eb97010c0b2d5607d6221366f.gif)
這里,標記 {6} 應(yīng)用于字符集 [0-9A-F]。
7.5 最小次和最大次之間
如果我們希望在最小次和最大次之間匹配一個特定標記,可以在這個標記后添加 {min,max}
。
![](/d/20211017/71e932b322b5a58b6bf60ddf1d2130c5.gif)
警告
{min,max}
中逗號后面不要有空格。
7.6 最少 x 次
如果我們希望匹配一個特定的標記最少 x 次,可以在標記后添加 {x,}。 和 {min, max} 類似,只是沒有上限了。
![](/d/20211017/048ebf1bf7b42cc9f347c3b8a562093a.gif)
7.7 貪婪模式的注意事項
正則表達式默認使用貪婪模式。在貪婪模式下,會盡可能多的匹配符合要求的字符。
![](/d/20211017/5cbdf6696b52920783aa793ce43f2baa.gif)
![](/d/20211017/15cd6a3edfeee1f5f420747ce305aee1.gif)
在**重復(fù)操作符(?,*,+,...)**后面添加 ?
,可以讓匹配變“懶”。
![](/d/20211017/988c5ae2949a8425c32ee3a8910b5aa5.gif)
在這里,這也可以通過使用[^"]
代替。(這是最好的做法)。
![](/d/20211017/481f1a3397b36f499903f3a8a63d4436.gif)
懶惰,意味著只要條件滿足,就立即停止;但貪婪意味著只有條件不再滿足才停止。
-Andrew S on StackOverflow
![](/d/20211017/d509203cd1796e98b6a6e152a0d11a14.gif)
![](/d/20211017/e5b4ded2cae8359e57ad0871da688cb5.gif)
7.8 例子
比特幣地址
/([13][a-km-zA-HJ-NP-Z0-9]{26,33})/g
(思考: {26,33}?呢)
![](/d/20211017/7fbab4dd5f6fae9d57f855ddd17a08bd.gif)
Youtube 視頻
/(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\&;.*?v=([^\s]+).*/gm
![](/d/20211017/018638df7e0c5067a46a0dcaae321d41.gif)
我們可以使用錨點調(diào)整表達式不讓它匹配最后一個不正確的鏈接,之后我們會接觸到。
8 交替
交替允許匹配幾個短語中的一個。這比僅限于單個字符的字符組更加強大。
使用管道符號 |
把多個短語之間分開
![](/d/20211017/70fa964791decc254c23ee9baa56c65f.gif)
匹配 foo, bar, 和 baz 中的一個。
如果正則中只有一部分需要“交替”,可以使用組進行包裹,捕獲組和非捕獲組都可以。
![](/d/20211017/546356542a3eed2553d73b1fec3d9ffc.gif)
Try 后面跟著 foo, bar, 和 baz 中的一個。
匹配 100-250 中間的數(shù)字:
![](/d/20211017/d638a2e2983a47619f3c7c38df6ccd26.gif)
這個可以使用 Regex Numeric Range Generator 工具生成。
例子
十六進制顏色
讓我們改進一下之前十六進制顏色匹配的例子。
/#[0-9A-F]{6}|[0-9A-F]{3}
![](/d/20211017/76995c6eed330a388dd3cbb91b2b78d3.gif)
[0-9A-F]{6}
要放在[0-9A-F]{3}
的前面,這一點非常重要。否則:
/#([0-9A-F]{3}|[0-9A-F]{6})/g
![](/d/20211017/29c9a4fcbf5d006ddf56239320c79b2d.gif)
小提示
正則表達式引擎是從左邊到右邊的嘗試交替的。
羅馬數(shù)字
/^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/g
![](/d/20211017/b9f2f65b3f9d6ba3306e5a5e07521b38.gif)
9 修飾符
修飾符允許我們把正則表達式分成不同的 "模式"。
修飾符是 /pattern/
后面的部分。
不同引擎支持不同的修飾符。在這里我們只討論最常見修飾符。
9.1 全局修飾符(g)
到現(xiàn)在為止,所有的例子都設(shè)置了全局修飾符。如果不啟用全局修飾符,正則表達式匹配第一個以后將不再匹配其他任何字符。
![](/d/20211017/a32e94641af00030d296cc345c3b5d68.gif)
![](/d/20211017/d7849d64d5a8bbf60ea7ef3775d4102f.gif)
9.2 不區(qū)分大小寫修飾符(i)
顧名思義,啟用這個修飾符會使正則在匹配時不區(qū)分大小寫。
![](/d/20211017/da6941b06d588b3e6536c1c28fa45c69.gif)
![](/d/20211017/1cf02b7ebe4c2dbfa0e634b936b906db.gif)
![](/d/20211017/81f33804baa33b281ad78209dc4193eb.gif)
9.3 多行模式修飾符(m)
有限支持
在 Ruby 中,m 修飾符是執(zhí)行其他的函數(shù)。
多行修飾符與正在在處理包含換行符的“多行”字符串時對錨點的處理有關(guān)。默認情況下,/^foo$/
只匹配 “foo”。
我們可能希望它在多行字符串中的一行也能匹配 foo。
我們拿 "bar\nfoo\nbaz"
舉例子:
bar foo baz
如果沒有 m 修飾符,上面的字符串會被當(dāng)做單行 bar\nfoo\nbaz
, 正則表達式 ^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)成始終要在字符串開頭匹配一個不可見的字符。
![](/d/20211017/777079c81a98f33a2392594018e92618.gif)
10.2 行尾
在正則結(jié)尾時插入$
號, 類似于行首符。你可以把它當(dāng)成始終要在字符串結(jié)尾匹配一個不可見的字符。
![](/d/20211017/bfc0d63c1d5d27def801e3c138d44881.gif)
^
和$
錨點經(jīng)常一起使用,以確保正則和字符串整個匹配,而不僅僅是部分匹配。
![](/d/20211017/5e370b6d2395c20e0844ed768459c6a6.gif)
讓我們回顧一下重復(fù)中的一個例子,并在正則的末尾添加兩個錨點。
![](/d/20211017/e04017c2b3ee3e69784606ac3a3d5457.gif)
如果沒有這 2 個錨點, http/2
和 shttp
也會被匹配。
10.3 字邊界 \b
字邊界是一個字符和非詞字符之間的位置。
字邊界錨點 \b
,匹配字符和非詞字符之間存在的假想不可見字符。
![](/d/20211017/0e966c747d92ec86db9e808df9cfb56f.gif)
提示
字符包括 a-z
, A-Z
, 0-9
, 和_
.
![](/d/20211017/c0cef7c88ac6c8baf177911ee71c8c3e.gif)
![](/d/20211017/7c696c690a400c310cfe158b7cceb2ef.gif)
還有一個非字邊界錨 \B
。
顧名思義,它匹配除字邊界之外的所有內(nèi)容。
![](/d/20211017/a0b3b6bfb0ecd12be573816d203be567.gif)
![](/d/20211017/c3b49e6fda04e7b97f39076cc6b6d627.gif)
小提示
^…$
和\b…\b
是常見的模式,您幾乎總是需要這 2 個防止意外匹配。
10.4 例子
尾部空格
![](/d/20211017/97e69ac087986a359e26cfb25f3d2312.gif)
markdown 標題
![](/d/20211017/3b31e1cf2ca6e5f7d3eca8fa95281534.gif)
沒有錨點:
![](/d/20211017/0e6111b31140b189f536b2140d86fade.gif)
11 零寬斷言(lookaround)
零寬斷言可用于驗證條件,而不匹配任何文本。
你只能看,不能動。
- 先行斷言(lookhead)
- 先行斷言(lookbehind)
11.1 先行斷言(lookhead)
正向(positive)
![](/d/20211017/9a36d4d1b7cce25afbff6ea437bb6fb5.gif)
注意后面的字符是如何不匹配的。可以通過正面前看得到證實。
/(.+)_(?=[aeiou])(?=\1)/g
![](/d/20211017/5cf2db1d4ff26f2b92fd819c397ad82b.gif)
正則引擎在 _
使用了 (?=[aeiou])
和 (?=\1)
進行檢查。
![](/d/20211017/35c0596d6ad92287d79a71678558a49f.gif)
負向(Negative)
![](/d/20211017/1aa039b33c6669d2803064f4c9d65ea8.gif)
![](/d/20211017/f018b3b2a04dc6ff2d69d8fe07c61cfc.gif)
如果沒有錨點,將匹配每個示例中沒有#的部分。
負向的先行斷言常常用于防止匹配特定短語。
![](/d/20211017/85ba19004b6ae00c7e07663819ee67d1.gif)
![](/d/20211017/34f244de520a7c2b34573b6ba9dc6351.gif)
11.2 例子
密碼驗證
/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/
![](/d/20211017/40996c1bb46f440b73070744f7984048.gif)
零寬斷言可用于驗證多個條件。
帶引號的字符串
![](/d/20211017/5dabb6db30c24b549360416ea25d2910.gif)
如果沒有先行斷言,我們最多只能做到這樣:
![](/d/20211017/b85d90f45e39ba7638ef5d5254774353.gif)
12 進階例子
JavaScript 注釋
/\/\*[\s\S]*?\*\/|\/\/.*/g
![](/d/20211017/82e967e6b4a23761483bd7a199d19255.gif)
[\s\S]
是一種匹配任何字符(包括換行符)的技巧。我們避免使用dot-all 修飾符,因為我們需要使用.
表示單行注釋。
24小時時間
/^([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$/g
![](/d/20211017/fee98ac78bf551c6bdb0bdebf9ca611e.gif)
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
![](/d/20211017/2f15a4373078a4f2741f918368adadc7.gif)
元標簽
/Example source="(.*?)" flags="(.*?)">/gm
![](/d/20211017/f9a30759bf7deb970377e54720213b19.gif)
替換: Example regex={/$1/$2}>
浮點數(shù)
![](/d/20211017/d4536d60f036953c7f537134ecb76f2c.gif)
正向的先行斷言 (?=\.\d|\d)
確保不會匹配 ..
HSL顏色
從0到360的整數(shù)
/^0*(?:360|3[0-5]\d|[12]?\d?\d)$/g
![](/d/20211017/7aaed5095d3c39adb08ebd77b068c5d4.gif)
百分比
/^(?:100(?:\.0+)?|\d?\d(?:\.\d+)?)%$/g
![](/d/20211017/181459d2cadd8c11acd1b81082b3971b.gif)
HSL 和 百分比
/^hsl\(\s*0*(?:360|3[0-5]\d|[12]?\d?\d)\s*(?:,\s*0*(?:100(?:\.0+)?|\d?\d(?:\.\d+)?)%\s*){2}\)$/gi
![](/d/20211017/d50fc5b17a4b8e8852751c1cd1ea113e.gif)
13 下一步
如果你像進一步學(xué)習(xí)正則表達式及其工作原理:
- awesome-regex
regex
tag on StackOverflow
- StackOverflow RegEx FAQ
- r/regex
- RexEgg
- Regular-Expressions.info
- Regex Crossword
- Regex Golf
謝謝閱讀!添加微信:手邊字節(jié)