一篇超詳細的正則表達式簡明指南


公司使用的markdown解析類庫一直有一些解析上的問題,最近給fix了。而markdown解析主要是對正則表達式的使用。中間惡補了一下相關知識,在此梳理一下。

什麼是正則表達式

正則表達式就是用一個“字符串”來描述一個特徵,然後去驗證另一個“字符串”是否符合這個特徵。簡單的一個例子:用字符串”a”來驗證字符串s是否是”a”,形如s.match(“a”)。概括來說有以下作用:

  • 驗證字符串是否符合指定特徵,比如驗證是否是合法的郵件地址。

  • 查找字符串,從一個長的文本中查找符合指定特徵的字符串,比查找固定字符串更加靈活。

  • 替換字符串,比普通的基於字符串的替換更靈活。

基本規則

普通字符

字母、數字、漢字、下劃線、以及後續沒有特殊定義的標點符號,都是”普通字符”。表達式中的普通字符,在匹配一個字符串的時候,匹配與之相同的一個字符。如:表達式 “a”,在匹配字符串 “abcde” 時,匹配到的內容是:“a”。

轉義字符

一些不便書寫的字符,採用在前面加 “\” 的方法。常見的如:

  • \r: 回車

  • \n: 換行符

  • \t: 製表符

  • \: “\” 本身

此外,還有其他再正則中有特殊用處的標點符號,在前面加 “\”後,代表該符號本身。如:^, “ 字符,需要寫成 “\^” 和 “$”。如

  • \^: 匹配^符號本身

  • \$: 匹配$符號本身

  • \.: 匹配小數點. 本身

其匹配規則和普通字符串是一樣的,如“\^”匹配“a^bc”中的“^”。

‘多種字符’匹配

  • \d:任意一個數字,0~9 中的任意一個

  • \w:任意一個字母或數字或下劃線,也就是 A~Z,a~z,0~9,_ 中任意一個

  • \s:包括空格、製表符、換頁符等空白字符的其中任意一個

  • .:小數點可以匹配除了換行符以外的任意一個字符

如:表達式 “\dtest\d”匹配”1test2”。

除了正則自帶的“多種字符”匹配外,還可以通過中括號[]來自定義。

  • 使用[ ]包含一系列字符,能夠匹配其中任意一個字符。

  • 用 [^ ]包含一系列字符,則能夠匹配其中字符之外的任意一個字符。

如:[123]匹配 “1”或”2”或“3”;[^abc]匹配 “a”、”b”、”c” 之外的任意一個字符。

這裏需要注意的是,在使用[]時,只有會改變字符組含義的才需要轉義,

  • 反斜線必須轉義

  • 方括號必須轉義

  • 「^」在首和「-」在中必須轉義

其他情況即使是特殊字符也不需要轉義,如:

  • [aeiou]

  • [$.*+?{}()|]

  • [abc^123-]

匹配次數

“次數修飾”放在”被修飾的正則表達式”後邊,可以匹配多次。如:

  • {n}: 表達式重複n次,比如:“\d{2}” 相當於 “\d\d”。

  • {m,n}:表達式至少重複m次,最多重複n次,比如:“a{1,3}”可以匹配 “a”或”aa”或”aaa”。

  • {m,}: 表達式至少重複m次,比如:“\d{2,}”可以匹配 “12”,“123”,“12345678”。

  • ?: 匹配表達式0次或者1次,相當於 {0,1},比如:“a[b]?”可以匹配”a”,“ab”。

  • +: 表達式至少出現1次,相當於 {1,},比如:“a+”可以匹配”a”,“aa”,“aaa”。

  • *: 表達式不出現或出現任意次,相當於 {0,},比如:”ab“可以匹配 “a”、”ab”、”abb”。

特殊符號

  • ^: 與字符串開始的地方匹配,不匹配任何字符,這裏如果使用(?m)模式,則匹配每一行的開始。如:“^aaa”無法匹配 “xxxaaaxxx”,可以匹配”aaaxxx”。

  • $: 與字符串結束的地方匹配,不匹配任何字符,這裏如果使用(?m)模式,則匹配每一行的結束。如:“aaa$”無法匹配“xxxaaaxxx”, 可以匹配“xxxaaa”。

  • \b:匹配一個單詞邊界,也就是單詞和空格之間的位置,不匹配任何字符。它與 “^”、”$“ 類似,本身不匹配任何字符,但是它要求它在匹配結果中所處位置的左右兩邊,其中一邊是 “\w” 範圍,另一邊是 非“\w” 的範圍。。如:“.\b.”匹配“@@abc”的“@a”。

此外,還有一些符號可以影響表達式內部的子表達式之間的關係:

  • |: 左右兩邊表達式之間“或”關係,匹配左邊或者右邊。

  • (): 在被修飾匹配次數的時候,括號中的表達式可以作爲整體被修飾;取匹配結果的時候,括號中的表達式匹配到的內容可以被單獨得到。如:”(ab\s*)+“匹配”hi, ab ab ab”中的“ab ab ab”。

高級規則

貪婪與非貪婪匹配

在使用修飾匹配次數的特殊符號時,有幾種表示方法可以使同一個表達式能夠匹配不同的次數,比如:“{m,n}”, “{m,}”, “?”, “*”“+”具體匹配的次數隨被匹配的字符串而定。這種重複匹配不定次數的表達式在匹配過程中,總是儘可能多的匹配。

比如,文本 “axxxaxxxa”,”(a)(\w+)“,其中”\w+“會匹配”xxxaxxxa”,”(a)(\w+)(a)“則會匹配”xxxaxxx”。由此可見,”\w+“ 在匹配的時候,總是儘可能多的匹配符合它規則的字符。

雖然第二個舉例中,它沒有匹配最後一個 “a”,但那也是爲了讓整個表達式能夠匹配成功。同樣的,帶 ”“ 和 ”{m,n}“ 的表達式都是儘可能地多匹配,帶 ”?“ 的表達式在可匹配可不匹配的時候,也是儘可能的匹配。這種匹配原則就叫作”貪婪”模式。

非貪婪模式則是指的在修飾匹配次數的特殊符號後再加上一個 “?” 號,可以使匹配次數不定的表達式儘可能少的匹配,使可匹配可不匹配的表達式,儘可能的不匹配

這種匹配原則也叫作 “勉強” 模式。如果少匹配就會導致整個表達式匹配失敗的時候,與貪婪模式類似,非貪婪模式會最小限度的再匹配一些,以使整個表達式匹配成功。如,文本 “axxxaxxxa” ,“(a)(\w+?)”,其中”\w+“只會匹配一個“x”。

反向引用

表達式在匹配時,表達式引擎會將小括號 “()” 包含的表達式所匹配到的字符串記錄下來。在獲取匹配結果的時候,小括號包含的表達式所匹配到的字符串可以單獨獲取。當用某種邊界來查找,而所要獲取的內容又不包含邊界時,必須使用小括號來指定所要的範圍。如:“

(.*?)

“即獲取div標籤內部的內容。


這裏小括號包含的正則表達式所匹配到的字符串不僅僅是在匹配結束後纔可以使用,在匹配過程中也可以使用。表達式後邊的部分,可以引用前面括號內的子匹配已經匹配到的字符串。引用方法是 “\” 加上一個數字。”\1” 引用第1對括號內匹配到的字符串,”\2” 引用第2對括號內匹配到的字符串,以此類推,而如果一對括號內包含另一對括號,則外層的括號先排序號。換句話說,哪一對的左括號 “(” 在前,那這一對就先排序號。

例如:表達式 “(‘|’)(.*?)(\1)“ 在匹配 ” Hello’, “World” “ 時,匹配結果是:成功;匹配到的內容是:” ‘Hello’ “。再次匹配下一個時,可以匹配到 ” “World” “。

預搜索

如前面所講”^“、”$“、”\b”字符有一個共同點,就是:它們本身不匹配任何字符,只是對 “字符串的兩頭” 或者 “字符之間的縫隙” 附加了一個條件。同樣的,正則中提供了其他基於此原理的機制,來實現預搜索。

  • 正向預搜索:”(?=xxxxx)“,”(?!xxxxx)”

    格式:”(?=xxxxx)“,在被匹配的字符串中,它對所處的 “縫隙” 或者 “兩頭” 附加的條件是:所在縫隙的右側,必須能夠匹配上xxxxx這部分的表達式,不影響後邊的表達式去真正匹配這個縫隙之後的字符。如:“Mac (?=book|air)” 在匹配 “Mac pro, Mac air” 時,將只匹配 “Mac air” 中的 “Mac”。

    格式:“(?!xxxxx)”,所在縫隙的右側,必須不能匹配 xxxxx 這部分表達式。如:“hello(?!\w)” 在匹配字符串 “hello,helloworld”時,匹配 hello”。這裏使用 “(?!\w)” 和使用 “\b” 效果一樣。

  • 反向預搜索:“(?<=xxxxx)”,“(?

    和正向預搜索類似,反向預搜索要求的條件是:所在縫隙的 “左側”,兩種格式分別要求必須能夠匹配和必須不能夠匹配指定表達式,而不是去判斷右側。與 “正向預搜索” 一樣的是:它們都是對所在縫隙的一種附加條件,本身都不匹配任何字符。

他通用規則

  • 可以使用 “\xXX” 和 “\uXXXX” 表示一個字符(”X” 表示一個十六進制數)

    • \xXX: 編號在 0-255 範圍的字符,如:空格可以使用 “\x20” 表示

    • \uXXXX: 任何字符可以使用 “\u” 再加上其編號的4位十六進制數表示,比如:“\u4E2D”

  • 在表達式 “\s”,”\d”,”\w”,”\b” 表示特殊意義的同時,對應的大寫字母表示相反的意義

    • \S: 匹配所有非空白字符

    • \D: 匹配所有的非數字字符

    • \W: 匹配所有的字母、數字、下劃線以外的字符

    • \B: 匹配非單詞邊界,即左右兩邊都是 “\w” 範圍或者左右兩邊都不是 “\w” 範圍時的字符縫隙

  • 括號“()”內的子表達式,如果希望匹配結果不進行記錄供以後使用,可以使用 “(?:xxxxx)”格式。如:表達式 “(?:(\w)\1)+” 匹配 “a bbccdd efg” 時,結果是 “bbccdd”。括號 “(?:)” 範圍的匹配結果不進行記錄,因此 “(\w)” 使用 “\1” 來引用。

  • 常用的表達式屬性設置包括:Ignorecase、Singleline、Multiline、Global

    • Ignorecase: 默認情況下,表達式中的字母是要區分大小寫的。配置爲 Ignorecase 可使匹配時不區分大小寫。有的表達式引擎,把 “大小寫” 概念延伸至 UNICODE 範圍的大小寫。

    • Singleline: 默認情況下,小數點 “.” 匹配除了換行符(\n)以外的字符。配置爲Singleline可使小數點可匹配包括換行符在內的所有字符。

    • Multiline: 默認情況下,表達式 “^” 和 “$” 只匹配字符串的開始1和結尾4位置。如:1xxxxxxxxx2\n 3xxxxxxxxx4

配置爲 Multiline 可以使 “^” 匹配1外,還可以匹配換行符之後,下一行開始前3的位置,”$“ 匹配4外,還可以匹配換行符之前,一行結束2的位置。使用(?m)可以設置爲Multiline模式。如”(?m)^\n +“。

  • Global: 主要在將表達式用來替換時起作用,配置爲Global表示替換所有的匹配。

提示

  • 如果要求表達式所匹配的內容是整個字符串,而不是其中的一部分,可以在表達式的首尾使用 “^” 和 ““ 要求整個字符串只有數字。

  • 如果要求匹配的內容是一個完整的單詞,而不會是單詞的一部分,那麼在表達式首尾使用 “\b”,如:使用 “\b(if|while|…)\b” 來匹配程序中的關鍵字。

  • 表達式不要匹配空字符串。否則會一直得到匹配成功,而結果什麼都沒有匹配到。

  • 能匹配空字符串的子匹配不要循環無限次。如果括號內的子表達式中的每一部分都可以匹配0次,而這個括號整體又可以匹配無限次,那麼匹配過程中可能死循環。

  • “|” 的左右兩邊,對某個字符應該只有一邊可以匹配,以防止”|“兩邊的表達式因爲交換位置而有所不同。

  • 要合理選擇貪婪模式與非貪婪模式,如. 與 .?的區別使用。

本文分享自微信公衆號 - 架構真經(gentoo666)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章