正規表示式從入門到高階

2023-08-29 12:01:54

序言

對於正則,許多程式設計師都覺得它很繁瑣,找不到頭緒。但其實只要明白了基礎語法,正則其實是非常簡單的。學習正規表示式一定要躬行實踐,自己動手來測試的自己表示式,這將大大有益對於正規表示式的掌握。在正文開始前,先給大家推薦一個好用的正則線上測試工具,本文後面將會使用它來對我們編寫的正規表示式做測試:https://c.runoob.com/front-end/854/

基礎語法

正規表示式的基本形式:/pattern/flags 。其中 pattern 是匹配規則,flage 被稱為修飾符。我們先來看一個簡單的範例:

正則:/you/g
文字:If you shed tears when you miss the sun, you also miss the stars
匹配結果:
you
you
you

提示:在每一個範例下面,可以通過上面提供的線上測試工具來自己測試一下,加深理解。線上測試工具的結果:

在上面的範例中,/g 被稱為全域性匹配,是一個常用修飾符,表示匹配字串中的所有元素,即匹配了3個 you

修飾符

修飾符用於標記正規表示式的額外策略,下面四個是常用的修飾符:

字元 描述
g 全域性匹配
i 忽略大小寫
m 多行模式
s 該模式下,.會匹配換行符\n

g 在所有的表示式中基本都需要攜帶,i 望文知義。 m 和 s 我們會在後文中逐漸的認識到,現在不必糾結他們。

元字元

元字元這個概念比較難以被理解,通常會直接勸退一批想學正規表示式的人。其實元字元說白了,就是規定一個普通字元具備特殊含義,用來匹配符合這個特殊含義的字元。我們先列舉出常用的一些元字元,逐項來看他們的所具備的特殊含義。

選擇與分支

字元 描述
sea|sky 匹配 sea 或者 sky,可以匹配若干個,如 sea|sky|stars
[abc] 匹配 a 和 b 和 c
[^abc] 匹配除了 a 和 b 和 c 之外的字元
[a-z] 匹配 a 到 c 之間的所有小寫字元,也可以使用[0-9]匹配數位範圍
[a-z] 匹配除了 a 到 c 之間的所有小寫字元,同理,也可以匹配數位範圍

以上五個選擇分支的匹配規則是很常用的匹配規則。通常,sea|sky 這種匹配方式會被 () 括起來:

() 也是一種元字元,通常用來把一組匹配規則括起來,表示一個分組。

基礎元字元

字元 描述
. 匹配任意字元,不包括換行符\n和\r,如果需要匹配 \n和\r ,可以使用修飾符:s
\d 匹配一個數位,相當於 [0-9]
\D 匹配非數位,相當於 [^0-9]
\s 匹配任意空白字元,相當於 [\t\n\r\f\v]
\S 匹配非空白字元,相當於 [^\t\n\r\f\v]
\w 匹配數位、字母、下劃線中任意一個字元,相當於 [a-zA-Z0-9_]
\W 匹配非數位、字母、下劃線中的任意字元,相當於 [^a-zA-Z0-9_]

\ 也是一個基礎元字元,用來將元字元轉換為普通字元,類似於程式語言中的跳脫。如真的要匹配 . 這個字元,應該使用 \. ,否則 . 將會被識別為匹配任意字元。正規表示式中需要的跳脫字元:* . ? + $ ^ [ ] ( ) { } | \

邊界元字元

還有一些基礎元字元:邊界元字元。其不佔用字元位置,只是表達一個邊界:

字元 描述
\b 匹配位於每個單詞的開始或結束位置
\B 匹配不是單詞開頭和結束的位置,即每個單詞的中間位置
^ 匹配開始位置,多行模式下匹配每一行的開始
$ 匹配結束位置,多行模式下匹配每一行的結束

\b\B 比較容易理解,\b 可以粗略的理解就是匹配單詞之間的空格,但是匹配結果不會攜帶這個空格。\B 則是和 \b 相反:

^& 匹配有一個很重要的概念:行。他們表示了一行的開始和結束位置。我們來看一個範例:

可以看到,加入了 ^ 後,an 的匹配只會從行首開始匹配,我們這裡加入 i 修飾符,表示不區分大小寫的匹配。& 同理:

為什麼說行是 ^& 的重要概念呢,我們來看一組範例。ok& 可以匹配以 ok 為結束的字元:

這顯然符合我們的預期,但是當我們在加入一行文字,匹配結果就會出現意外:

按照道理說,應該能匹配到兩個 ok 字元,但是這裡只匹配到了最後一行,如果需要匹配多行資料,則需要加入一個修飾符:m (多行模式)。讓我們看一下加入多行模式後的匹配結果:

這樣就符合了我們的預期情況。

重複匹配

字元 描述
* 匹配前面的子表示式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等價於{0,}。
+ 匹配前面的子表示式一次或多次。例如,zo+ 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等價於 {1,}。
? 匹配前面的子表示式零次或一次。例如,do(es)? 可以匹配 "do" 或 "does" 。? 等價於 {0,1}。
{n} n 是一個非負整數。匹配確定的 n 次。例如,o{2} 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的兩個 o。
{n,} n 是一個非負整數。至少匹配 n 次。例如,o{2,} 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。o{1,} 等價於 o+。o{0,} 則等價於 o*。
{n,m} m 和 n 均為非負整數,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,o{1,3} 將匹配 "fooooood" 中的前三個 o。o{0,1} 等價於 'o?'。請注意在逗號和兩個數之間不能有空格。

在該匹配的模式下,還有一個特殊的元字元:。被稱為非貪婪模式。注意,該 和上文的匹配零次或一次完全不同。非貪婪模式的元字元 ? 只能跟在上述六個的重複匹配後面。例如,我們想要匹配出一段文字中的以 http 或者 https 開頭的圖片地址:

範例文字中存在兩個 .png 的圖片,我們使用 (http:https) 來匹配 http 或者 https 開頭,然後使用 . 來匹配所有字元,再加上 + ,表示一次或多次匹配,最後使用 (.png) 表示匹配 .png 結束。但這樣的結果就是當我們遇到第一個 .png 時,.+ 也會匹配到 .png,所以就導致了獲取了錯誤的匹配結果。遇到這種情況,我們就要使用非貪婪模式,在 + 後面加上 ? :

可以看到,此時獲取了我們想要的結果。非貪婪模式下,會盡可能少的匹配字串

到此,我們已經掌握了正規表示式的基礎,可以找一些實際的業務需求來加深理解。比如說檢測郵箱地址是否合法:

\w 會匹配所有數位、字母下劃線, + 表示至少需要匹配到一個字元,我們要求它需要到 @ 時停下,所以加上 ? 非貪婪模式。這樣就可以成功匹配到 tyyn1022@ 。繼續檢測後面的是否符合要求,同樣使用 \w+? 匹配域名名稱,\. 表示匹配文字 . ,最後加上 (com|net|top|org),即要求頂級域名必須是 com、net、top、org。這樣就匹配到了剩餘的部分:163.com

零寬斷言

零寬斷言是正規表示式的高階用法,它一種特殊的元字元。實際作用就如同它的名字:零寬與斷言。簡而言之,就是匹配某個字元的前後,卻又不想匹配到這個字元本身(零寬的意思)。零寬斷言分為四種:

字元 描述
(?=pattern) 正向先行斷言。例如,foo(?=bar) 會匹配 foobar 中的 foo。
(?!pattern) 反向先行斷言。例如,foo(?!bar) 會匹配 foobaz 中的 foo,但不會匹配 foobar 中的 foo。
(?<=pattern) 正向後行斷言。例如,(?<=foo)bar 會匹配 foobar 中的 bar,但不會匹配 bazbar 中的 bar,因為它前面不是 foo。
(?<!pattern) 反向後行斷言。例如,(?<!foo)bar 會匹配 bazbar 中的 bar,但不會匹配 foobar 中的 bar。

我們來舉一個實際的例子,我們需要從一段富文字文字中匹配所有以 http:// 或者 https:// 開頭,以 .png 或者 .jpg 結尾的圖片地址,但是要求不能把 http:// 和 https:// 匹配進去

/((?<=http://)|(?<=https://)).+?(.png|.jpg)/ig

該正規表示式即可達到我們想要的效果:

在該表示式中分為三段:

  1. ((?<=http://)|(?<=https://)),正向後行斷言,表示匹配以 http:// 或者 https:// 開頭的字元
  2. .+?,匹配除了換行符 \n 和 \r 之外的所有字元至少一次,且是非貪婪模式,為什麼這裡使用貪婪模式,可以自己動手試一下
  3. (.png|.jpg) 表示以 .png 或者 .jpg 結尾