這個教程,真的讓我學會了正規表示式

和牛發表於2020-05-08

這個教程,真的讓我學會了正規表示式

這是一篇翻譯文章。我學過很多次正規表示式,總是學了忘,忘了學,一到用的時候還是隻能靠搜尋引擎。

這回看到這個正則教程,感覺非常驚喜。嘗試翻譯了一遍,譯得不好,大家可以看原文,很容易理解。

原文地址:https://refrf.shreyasminocha.me/

1 介紹

正規表示式允許定義一種模式,並通過這種模式針對字串執行對應的操作。與模式匹配的子字串稱為“匹配”。

正規表示式是定義搜尋模式的一串字元。

正規表示式主要用在如下場景:

  • 輸入驗證
  • 查詢替換操作
  • 高階字串操作
  • 檔案搜尋或重新命名
  • 白名單和黑名單

正規表示式不太適合用在這些場景:

  • XML 或 HTML 解析
  • 完全匹配的日期

有許多實現正則匹配的引擎,每種都有自己的特性。這本書將避免討論(不同引擎之間的)特性差異,而是隻討論在大多數情況下不同引擎都共有的特徵。

整本書中的示例使用JavaScript。因此,這本書可能會稍微偏向 JavaScript 的正則引擎。

2 基礎

正規表示式通常格式化為 /<rules>/<flags>,通常為了簡潔而省略後面的 /<flags>。關於 flag 我們將在下一章詳細討論。

讓我們從/p/g 這個正規表示式開始。現在,請將 /g flag 視為固定不變的。

  • /p/g

如我們所見,/p/g 匹配所有小寫的 p 字元。

注意

預設情況下,正規表示式區分大小寫。

在輸入字串中找到的正規表示式模式的例項稱為“匹配”。

  • /pp/g

3 字元組

可以從一組字元中匹配一個字元。

  • /[aeiou]/g

[aeiou]/g 匹配輸入字串中的所有母音。

下面是另一個例子:

  • /p[aeiou]t/g

我們匹配一個 p,後跟一個母音,然後是一個 t。

有一個更直觀的快捷方式,可以在一個連續的範圍內匹配一個字元。

  • /[a-z]/g

警告

表示式 /[a-z]/g 只匹配一個字元。在上面的示例中,每個字元都有一個單獨的匹配項。不是整個字串匹配。

我們也可以在正規表示式中組合範圍和單個字元。

  • /[A-Za-z0-9_-]/g

我們的正規表示式 /[A-Za-z0-9_-]/g 匹配一個字元,該字元必須(至少)是以下字元之一:

  • A-Z
  • a-z
  • 0-9
  • _ 或者 -

我們也可以“否定”這些規則:

  • /[^aeiou]/g

/[aeiou]/g/[^aeiou]/g 之間的唯一區別是 ^ 緊跟在左括號之後。其目的是"否定"括號中定義的規則。它表示的意思是:

匹配任何不屬於a、e、i、o和 u 的字元

3.1 例子

非法的使用者名稱字元

  • /[^a-zA-Z_0-9-]/g

指定字元

/[A-HJ-NP-Za-kmnp-z2-9]/g

4 字元轉義

字元轉義是對某些通用字元類的簡略表達方式。

4.1 數字字元 \d

轉義符 \d 表示匹配數字字元 0-9。等同於 [0-9]

  • /\d/g (這裡請仔細看)

regular_expression_10-fs8.pngregular_expression_10-fs8.png

  • /\d\d/g

regular_expression_11-fs8.pngregular_expression_11-fs8.png

\D\d 的反面,相當於[^0-9]

  • /\D/g

regular_expression_12-fs8.pngregular_expression_12-fs8.png

4.2 單詞字元 \w

轉義符 \w 匹配單詞字元。包括:

  • 小寫字母 a-z
  • 大寫字母 A-Z
  • 數字 0-9
  • 下劃線 _

等價於 [a-zA-Z0-9_]

  • /\w/g

regular_expression_13-fs8.pngregular_expression_13-fs8.png

  • /\W/g

regular_expression_14-fs8.pngregular_expression_14-fs8.png

4.3 空白字元 \s

轉義符 \s匹配空白字元。具體匹配的字符集取決於正規表示式引擎,但大多數至少包括:

  • 空格
  • tab 製表符 \t
  • 回車 \r
  • 換行符 \n
  • 換頁 \f

其他還可能包括垂直製表符(\v)。Unicode自識別引擎通常匹配分隔符類別中的所有字元。然而,技術細節通常並不重要。

  • /\s/g

regular_expression_15-fs8.pngregular_expression_15-fs8.png

  • /\S/g (大寫 s)

regular_expression_16-fs8.pngregular_expression_16-fs8.png

4.4 任意字元 .

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

  • /./g

regular_expression_17-fs8.pngregular_expression_17-fs8.png

5 轉義

在正規表示式中,有些字元有特殊的含義,我們將在這一章中進行探討:

  • |
  • {,}
  • (,)
  • [,]
  • ^, $
  • +, *, ?
  • \
  • . 只在字元類中的字面量。
  • - : 有時是字元類中的特殊字元。

當我們想通過字面意思匹配這些字元時,我們可以再這些字元前面加 \ “轉義”它們。

  • /\(paren\)/g

regular_expression_18-fs8.pngregular_expression_18-fs8.png

  • /(paren)/g

regular_expression_19-fs8.pngregular_expression_19-fs8.png

  • /example\.com/g

regular_expression_20.pngregular_expression_20.png

  • /example.com/g

regular_expression_21.pngregular_expression_21.png

  • /A\+/g

regular_expression_22-fs8.pngregular_expression_22-fs8.png

  • /A+/g

regular_expression_23-fs8.pngregular_expression_23-fs8.png

  • /worth \$5/g

regular_expression_24-fs8.pngregular_expression_24-fs8.png

  • /worth $5/g

regular_expression_25-fs8.pngregular_expression_25-fs8.png

5.1 例子

JavaScript 內聯註釋

  • /\/\/.*

regular_expression_26-fs8.pngregular_expression_26-fs8.png

星號包圍的子串

  • /*[^\*]*\*

regular_expression_27-fs8.pngregular_expression_27-fs8.png

第一個和最後一個星號是字面上的,所有他們要用 \* 轉義。字符集裡面的星號不需要被轉義,但為了清楚起見,我還是轉義了它。緊跟在字符集後面的星號表示字符集的重複,我們將在後面的章節中對此進行探討。

6 組

顧名思義,組是用來“組合”正規表示式的元件的。這些組可用於:

  • 提取匹配的子集
  • 重複分組任意次數
  • 參考先前匹配的子字串
  • 增強可讀性
  • 允許複雜的替換

這一章我們先學組如何工作,之後的章節還會有更多例子。

6.1 捕獲組

捕獲組用(…)表示。下面是一個解釋性的例子:

  • /a(bcd)e/g

regular_expression_28-fs8.pngregular_expression_28-fs8.png

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

  • /\{([^{}]*)\}/g

regular_expression_29-fs8.pngregular_expression_29-fs8.png

通過語言的正則函式,您將能夠提取括號之間匹配的文字。

捕獲組還可以用於對正規表示式進行部分分組,以便於重複。雖然我們將在接下來的章節中詳細介紹重複,但這裡有一個示例演示了組的實用性。

  • /a(bcd)+e/g

regular_expression_30-fs8.pngregular_expression_30-fs8.png

其他時候,它們用於對正規表示式的邏輯相似部分進行分組,以提高可讀性。

  • /(\d\d\d\d)-W(\d\d)/g

regular_expression_31-fs8.pngregular_expression_31-fs8.png

6.2 回溯

回溯允許引用之前捕獲的子字串。

匹配第一組可以使用 \1,匹配第二組可以使用 \2,依此類推…

  • /([abc])×\1×\1/g

regular_expression_32-fs8.pngregular_expression_32-fs8.png

不能使用回溯來減少正規表示式中的重複。它們指的是組的匹配,而不是模式。

  • /[abc][abc][abc]/g

regular_expression_33-fs8.pngregular_expression_33-fs8.png

  • /[abc]\1\1/g

regular_expression_34-fs8.pngregular_expression_34-fs8.png

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

  • /\w+([,|])\w+\1\w+/g

regular_expression_35-fs8.pngregular_expression_35-fs8.png

這不能通過重複的字元類來實現。

  • /\w+[,|]\w+[,|]\w+/g

regular_expression_36-fs8.pngregular_expression_36-fs8.png

6.3 非捕獲組

非捕獲組與捕獲組非常相似,只是它們不建立“捕獲”。而是採取形式 (?: ...)

非捕獲組通常與捕獲組一起使用。也許您正在嘗試使用捕獲組提取匹配的某些部分。而你可能希望使用一個組而不擾亂捕獲順序,這時候你應該使用非捕獲組。

6.4 例子

查詢字串引數

  • /^\?(\w+)=(\w+)(?:&(\w+)=(\w+))*$/g

regular_expression_37-fs8.pngregular_expression_37-fs8.png

我們單獨匹配第一組鍵值對,因為這可以讓我麼使用 & 分隔符, 作為重複組的一部分。

(基礎的) HTML 標籤

根據經驗,不要使用正規表示式來匹配 XML/HTML。不過,我還是提供相關的一個例子:

  • /<([a-z]+)+>(.*)<\/\1>/gi

regular_expression_38-fs8.pngregular_expression_38-fs8.png

姓名

查詢:\b(\w+) (\w+)\b

替換: 1

在替換操作,經常使用 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

回溯和複數

查詢: \bword(s?)\b

替換: phrase$1

替換之前

This is a paragraph with some words.

Some instances of the word "word" are in their plural form: "words".

Yet, some are in their singular form: "word".

替換之後

This is a paragraph with some phrases.

Some instances of the phrase "phrase" are in their plural form: "phrases".

Yet, some are in their singular form: "phrase".

7 重複

重複是一個強大而普遍的正規表示式特性。在正規表示式中有幾種表示重複的方法。

7.1 可選項

我們可以使用 ?將某一部分設定成可選的(0或者1次)。

  • /a?/g

regular_expression_39-fs8.pngregular_expression_39-fs8.png

另一個例子:

  • /https?/g

regular_expression_40-fs8.pngregular_expression_40-fs8.png

我們還可以讓捕獲組和非捕獲組程式設計可選的。

  • /url: (www\.)?example\.com/g

regular_expression_41-fs8.pngregular_expression_41-fs8.png

7.2 零次或者多次

如果我們希望匹配零個或多個標記,可以用 * 作為字尾。

  • /a*/g

regular_expression_42-fs8.pngregular_expression_42-fs8.png

我們的正規表示式甚至匹配一個空字串。

7.3 一次或者多次

如果我們希望匹配 1 個或多個標記,可以用 + 作為字尾。

  • /a+/g

regular_expression_43-fs8.pngregular_expression_43-fs8.png

7.4 精確的 x 次

如果我們希望匹配特定的標記正好x次,我們可以新增{x}字尾。這在功能上等同於複製貼上該標記 x 次。

  • /a{3}/g

regular_expression_44-fs8.pngregular_expression_44-fs8.png

下面是匹配大寫的六個字元的十六進位制顏色程式碼的例子。

  • /#[0-9A-F]{6}/g

regular_expression_45-fs8.pngregular_expression_45-fs8.png

這裡,標記 {6} 應用於字符集 [0-9A-F]。

7.5 最小次和最大次之間

如果我們希望在最小次和最大次之間匹配一個特定標記,可以在這個標記後新增 {min,max}

  • /a{2,4}/g

regular_expression_46-fs8.pngregular_expression_46-fs8.png

警告

{min,max} 中逗號後面不要有空格。

7.6 最少 x 次

如果我們希望匹配一個特定的標記最少 x 次,可以在標記後新增 {x,}。 和 {min, max} 類似,只是沒有上限了。

  • /a{2,}/g

regular_expression_47-fs8.pngregular_expression_47-fs8.png

7.7 貪婪模式的注意事項

正規表示式預設使用貪婪模式。在貪婪模式下,會盡可能多的匹配符合要求的字元。

  • /a*/g

regular_expression_48-fs8.pngregular_expression_48-fs8.png

  • /".*"/g

regular_expression_49-fs8.pngregular_expression_49-fs8.png

在**重複操作符(?,*,+,...)**後面新增 ,可以讓匹配變“懶”。

  • /".*?"/g

regular_expression_50-fs8.pngregular_expression_50-fs8.png

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

  • /"[^"]*"/g

regular_expression_51-fs8.pngregular_expression_51-fs8.png

懶惰,意味著只要條件滿足,就立即停止;但貪婪意味著只有條件不再滿足才停止。

-Andrew S on StackOverflow

  • /<.+>/g

regular_expression_52-fs8.pngregular_expression_52-fs8.png

  • /<.+?>/g

regular_expression_53-fs8.pngregular_expression_53-fs8.png

7.8 例子

比特幣地址

  • /([13][a-km-zA-HJ-NP-Z0-9]{26,33})/g (思考: {26,33}?呢)

regular_expression_54-fs8.pngregular_expression_54-fs8.png

Youtube 視訊

  • /(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\?.*?v=([^&\s]+).*/gm

regular_expression_55-fs8.pngregular_expression_55-fs8.png

我們可以使用錨點調整表示式不讓它匹配最後一個不正確的連結,之後我們會接觸到。

8 交替

交替允許匹配幾個短語中的一個。這比僅限於單個字元的字元組更加強大。

使用管道符號 | 把多個短語之間分開

  • /foo|bar|baz/g

regular_expression_56-fs8.pngregular_expression_56-fs8.png

匹配 foo, bar, 和 baz 中的一個。

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

  • /Try (foo|bar|baz)/g

regular_expression_57-fs8.pngregular_expression_57-fs8.png

Try 後面跟著 foo, bar, 和 baz 中的一個。

匹配 100-250 中間的數字:

  • /1\d\d|2[0-4]\d|250/g

regular_expression_58-fs8.pngregular_expression_58-fs8.png

這個可以使用 Regex Numeric Range Generator 工具生成。

例子

十六進位制顏色

讓我們改進一下之前十六進位制顏色匹配的例子。

  • /#[0-9A-F]{6}|[0-9A-F]{3}

regular_expression_59-fs8.pngregular_expression_59-fs8.png

[0-9A-F]{6} 要放在[0-9A-F]{3}的前面,這一點非常重要。否則:

  • /#([0-9A-F]{3}|[0-9A-F]{6})/g

regular_expression_60-fs8.pngregular_expression_60-fs8.png

小提示

正規表示式引擎是從左邊到右邊的嘗試交替的。

羅馬數字

  • /^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/g

regular_expression_61-fs8.pngregular_expression_61-fs8.png

9 修飾符

修飾符允許我們把正規表示式分成不同的 "模式"。

修飾符是 /pattern/ 後面的部分。

不同引擎支援不同的修飾符。在這裡我們只討論最常見修飾符。

9.1 全域性修飾符(g)

到現在為止,所有的例子都設定了全域性修飾符。如果不啟用全域性修飾符,正規表示式匹配第一個以後將不再匹配其他任何字元。

  • /[aeiou]/g

regular_expression_62-fs8.pngregular_expression_62-fs8.png

  • /[aeiou]/

regular_expression_63-fs8.pngregular_expression_63-fs8.png

9.2 不區分大小寫修飾符(i)

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

  • /#[0-9A-F]{6}/i

regular_expression_64-fs8.pngregular_expression_64-fs8.png

  • /#[0-9A-F]{6}/

regular_expression_65-fs8.pngregular_expression_65-fs8.png

  • /#[0-9A-Fa-f]{6}/

regular_expression_66-fs8.pngregular_expression_66-fs8.png

9.3 多行模式修飾符(m)

有限支援

在 Ruby 中,m 修飾符是執行其他的函式。

多行修飾符與正在在處理包含換行符的“多行”字串時對錨點的處理有關。預設情況下,/^foo$/只匹配 “foo”。

我們可能希望它在多行字串中的一行也能匹配 foo。

我們拿 "bar\nfoo\nbaz" 舉例子:

bar
foo
baz

如果沒有 m 修飾符,上面的字串會被當做單行 bar\nfoo\nbaz, 正規表示式 ^foo$ 匹配不到任何字元。

如果有 m 修飾符,上面的字串會被當做 3 行。 ^foo$ 可以匹配到中間那一行。

9.4 Dot-all修飾符 (s)

有限支援

ES2018 之前的 JavaScript 不支援這個修飾符。 Ruby 也不支援這個修飾,而是用 m 表示。

.通常匹配除換行符以外的任何字元。使用dot all修飾符後,它也可以匹配換行符。

10 錨點

錨點本身不匹配任何東西。但是,他們會限制匹配出現的位置。

你可以把錨點當做是 "不可見的字元"。

10.1 行首 ^

在正則開始時插入^ 號,使正則其餘部分必須從字串開始的地方匹配。你可以把它當成始終要在字串開頭匹配一個不可見的字元。

  • /^p/g

regular_expression_67-fs8.pngregular_expression_67-fs8.png

10.2 行尾

在正則結尾時插入$ 號, 類似於行首符。你可以把它當成始終要在字串結尾匹配一個不可見的字元。

  • /p$/g

regular_expression_68-fs8.pngregular_expression_68-fs8.png

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

  • /^p$/g

regular_expression_69-fs8.pngregular_expression_69-fs8.png

讓我們回顧一下重複中的一個例子,並在正則的末尾新增兩個錨點。

  • /^https?$/g

regular_expression_70-fs8.pngregular_expression_70-fs8.png

如果沒有這 2 個錨點, http/2shttp 也會被匹配。

10.3 字邊界 \b

字邊界是一個字元和非詞字元之間的位置。

字邊界錨點 \b,匹配字元和非詞字元之間存在的假想不可見字元。

  • /\bp/g

regular_expression_71-fs8.pngregular_expression_71-fs8.png

提示

字元包括 a-z, A-Z, 0-9, 和_.

  • /\bp\b/g

regular_expression_72-fs8.pngregular_expression_72-fs8.png

  • /\bcat\b/g

regular_expression_73-fs8.pngregular_expression_73-fs8.png

還有一個非字邊界錨 \B

顧名思義,它匹配除字邊界之外的所有內容。

  • /\Bp/g

regular_expression_74-fs8.pngregular_expression_74-fs8.png

  • /\Bp\B/g

regular_expression_75-fs8.pngregular_expression_75-fs8.png

小提示

^…$\b…\b是常見的模式,您幾乎總是需要這 2 個防止意外匹配。

10.4 例子

尾部空格

  • /\s+$/gm

regular_expression_76-fs8.pngregular_expression_76-fs8.png

markdown 標題

  • /^## /gm

regular_expression_77-fs8.pngregular_expression_77-fs8.png

沒有錨點:

  • /## /gm

regular_expression_78-fs8.pngregular_expression_78-fs8.png

11 零寬斷言(lookaround)

零寬斷言可用於驗證條件,而不匹配任何文字。

你只能看,不能動。

  • 先行斷言(lookhead)
    • 正向(?=…)
    • 負向(?!…)
  • 先行斷言(lookbehind)
    • 正向(?<=…)
    • 負向(?<!…)

11.1 先行斷言(lookhead)

正向(positive)

  • /_(?=[aeiou])/g

regular_expression_79-fs8.pngregular_expression_79-fs8.png

注意後面的字元是如何不匹配的。可以通過正面前看得到證實。

  • /(.+)_(?=[aeiou])(?=\1)/g

regular_expression_80-fs8.pngregular_expression_80-fs8.png

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

  • /(?=.*#).*/g

regular_expression_81-fs8.pngregular_expression_81-fs8.png

負向(Negative)

  • /_(?![aeiou])/g

regular_expression_82-fs8.pngregular_expression_82-fs8.png

  • /^(?!.*#).*$/g

regular_expression_83-fs8.pngregular_expression_83-fs8.png

如果沒有錨點,將匹配每個示例中沒有#的部分。

負向的先行斷言常常用於防止匹配特定短語。

  • /foo(?!bar)/g

regular_expression_84-fs8.pngregular_expression_84-fs8.png

  • /---(?:(?!---).)*---/g

regular_expression_85-fs8.pngregular_expression_85-fs8.png

11.2 例子

密碼驗證

/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/

regular_expression_86-fs8.pngregular_expression_86-fs8.png

零寬斷言可用於驗證多個條件。

帶引號的字串

  • /(['"])(?:(?!\1).)*\1/g

regular_expression_87-fs8.pngregular_expression_87-fs8.png

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

  • /(['"])[^'"]*\1/g

regular_expression_88-fs8.pngregular_expression_88-fs8.png

12 進階例子

JavaScript 註釋

  • /\/\*[\s\S]*?\*\/|\/\/.*/g

regular_expression_89-fs8.pngregular_expression_89-fs8.png

[\s\S]是一種匹配任何字元(包括換行符)的技巧。我們避免使用dot-all 修飾符,因為我們需要使用. 表示單行註釋。

24小時時間

  • /^([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$/g

regular_expression_90-fs8.pngregular_expression_90-fs8.png

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

regular_expression_91-fs8.pngregular_expression_91-fs8.png

元標籤

  • /<Example source="(.*?)" flags="(.*?)">/gm

regular_expression_92-fs8.pngregular_expression_92-fs8.png

替換: <Example regex={/$1/$2}>

浮點數

  • 可選符號

  • 可選整數部分

  • 可選小數部分

  • 可選指數部分

  • /^([+-]?(?=\.\d|\d)(?:\d+)?(?:\.?\d*))(?:[eE]([+-]?\d+))?$/g

regular_expression_93-fs8.pngregular_expression_93-fs8.png

正向的先行斷言 (?=\.\d|\d) 確保不會匹配 ..

HSL顏色

從0到360的整數

  • /^0*(?:360|3[0-5]\d|[12]?\d?\d)$/g

regular_expression_94-fs8.pngregular_expression_94-fs8.png

百分比

  • /^(?:100(?:\.0+)?|\d?\d(?:\.\d+)?)%$/g

regular_expression_95-fs8.pngregular_expression_95-fs8.png

HSL 和 百分比

  • /^hsl\(\s*0*(?:360|3[0-5]\d|[12]?\d?\d)\s*(?:,\s*0*(?:100(?:\.0+)?|\d?\d(?:\.\d+)?)%\s*){2}\)$/gi

regular_expression_96-fs8.pngregular_expression_96-fs8.png

13 下一步

如果你像進一步學習正規表示式及其工作原理:

謝謝閱讀!新增微信:手邊位元組

相關文章