Regex(正則表達式)的解題技巧:快速理解與實作

在這篇文章中,我介紹了正則表達式(Regex)的基本概念和實作方法。透過具體的例子和說明,我解釋了如何使用 Regex 進行字串搜索、替換以及數據驗證等操作,並探討了其在不同編程環境中的適用性。我也提供了一些實用的線上工具推薦,以幫助理解和視覺化 Regex 的匹配過程,讓讀者能夠更有效地運用這一強大的工具。

Regex(正則表達式)的解題技巧:快速理解與實作
Photo by Giammarco Boscaro / Unsplash
這篇文章會介紹 Regex 正則表達式的功能與寫法,以及簡單實作
  1. Regular Expression 常縮寫為 Regexp、Regex 或 RE
  2. 中文則有正則表達式、正規表達式、常規表示式等譯名
  3. 用在搜尋字串,或搜尋到字串中的特定字串片段後再將其做全面性的代換
Regex 源自代數學上的 regular sets (正則集合),後被廣泛運用於多種程式語言中。
  • 使用 Regex 有三大優點
  1. 語法精簡:在不同的程式語言都可以使用一樣的表達式
  2. 速度較快:效能比用原來的字串方法快
  3. 支援度佳:目前很多工具都可以支援正規表達式
以下以 JavaScript 環境使用 Regex,不同程式語言差異只在儲存字串的變數使用上罷了

其實沒有很難,就是平舖直述照順序打你要搜尋的東西,難在特定符號和規則的簡寫,然後這些簡寫都要加上跳脫字元 \ 區別,所以你會看到很多的 \。如果這些規則一大串又簡寫時就算是高手也很難瞬間看懂。

※ 註


一、用 Regex 做字串搜尋的兩種方式

1. regex.test & regex.exec

const regex = /hello world/i

// 使用 test 比對字串是否符合 pattern,回傳 boolean
regex.test('Hello World !!') // true

// 使用 exec 取得比對的詳細資訊,比對失敗時回傳 null
regex.exec('Hello World !!') // ["Hello World", index: 0, input: "Hello World !!", groups: undefined]
regex.exec('Hello Regex !!') // null

2. 字串內建函式 string.search(regex) & string.match(regex)

const str = 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.'

// 使用 search 搜尋字串是否在段落中,有找到回傳字串的起始位置,沒找到回傳 -1
str.search('tExT') // -1
str.search(/tExT/i) // 28

// 使用 match 找出第一個比對成功的詳細資訊,加上 g flag 則會列出所有比對成功的字串
str.match(/ing/) // ["ing", index: 45, ...]
str.match(/ing/g) // ["ing", "ing"]

二、搜尋字串是否有片斷跟 Regex 中條件同

其實是非常直觀,就照順序寫下去,難得是在裡面有很多特殊字元 & 規則簡寫而已

// 搜尋字串 ‘gfgggggaaadggd‘ 是否有 aaa
const regex = /aaa/

// 搜尋字串 ‘SFf3234343‘ 是否有英文字大寫,用 [] 表示特定組合
const regex = /[ABCDEFGHIJKLMNOPQRSTUVWXYZ]/

// 搜尋字串 ‘SFf3234343‘ 是否有英文字大寫,可以這樣表示
const regex = /[A-Z]/

// 搜尋字串 ‘SFf3234343‘ 是否有英文或數字,可以這樣表示
const regex = /[A-Za-z0-9]/

// 搜尋字串 ‘SFf3234343‘ 是否「沒有」英文或數字,可以這樣表示
const regex = /[^A-Za-z0-9]/

三、搜尋字串中特定字的簡寫

// 比對換行符號外的任意一個字元
const regex = /./   

// 比對一個數字,相等於 /[0-9]/
const regex = /\d/  

// 比對一個英文、數字或底線,相等於 /[A-Za-z0-9_]/
const regex = /\w/  

// 比對一個的空格 (ex: space, tab, 換行, ...)
const regex = /\s/  

四、搜尋字串中特定複數的字

// 搜尋字串 ‘SFf35533234343‘ 是否有三個數字
const regex = /\d\d\d/

// 搜尋字串 ‘gfgggggaaadggd‘ 是否有三個數字
const regex = /\d{3}/

// 搜尋字串 ‘SFf3234343‘ 是否有三個字元接五個數字
const regex = /\w{3}\d{5}/

// {2, 5} 表示搜尋字元是否在字串連續出現 2 ~ 5 次
const regex = /^\w{2,5}/

// 使用 ? 表示搜尋字元是否在字串出現 0 或 1 次,等同於 {0,1}
const regex = /\w?/

// 使用 + 表示搜尋字元是否在字串出現 1 次或以上,等同於 {1,}
const regex = /\w+/

// 使用 * 表示搜尋字元是否在字串出現 0 次或以上,等同於 {0,}
const regex = /\w*/

// '+' 表示搜尋 a 出現的次數越多優先
// 搜尋字串 'a+++++' 搜尋到 'a+++++'
const regex = /a\+{2,}/

// '+' 表示搜尋 a 出現的次數越少優先
// 搜尋字串 'a+++++' 搜尋到 'a++'
const regex = /a\+{2,}?/

五、搜尋字首字尾或者搜尋兩個字

// ^  搜尋字串 ‘gfgggggaaadggd‘ 是否有 gf 在字首
const regex = /^gf/

// $  搜尋字串 ‘gfgggggaaadggd‘ 是否有 gf 在字尾
const regex = /gf$/

// |  搜尋字串 ‘gfgggggaaadggd‘ 是否有 gf 或者 gggg
const regex = /gf | gggg/

六、進階

以上大概就是基礎要弄懂 Regex 要弄懂的東西,其實沒有很難。接下來的是些比較進階的規則,基本上就會比較不直觀了。

1. 群組 (Capturing Group)

除了比對文字外也可以透過使用 Group 來捕獲特定的文字,也就是符合條件的字串片斷會可以幫你存在群組中,而群組你也可以命名更直觀。

// 假設要抓出使用者的 firstName 與 lastName,不命名下
const regex = /fullName: (\w+) (\w+)/
const str = 'fullName: Alan Hsu'
str.match(regex) 

/*
0: "fullName: Alan Hsu"
1: "Alan Hsu"
2: "Alan"
3: "Hsu"
groups: undefined
index: 0
input: "fullName: Alan Hsu"
length: 4
*/

// 假設要抓出使用者的 firstName 與 lastName,命名群組為 firstName 與 lastName 
const regex = /fullName: (?<firstName>\w+) (?<lastName>\w+)/
const str = 'fullName: Alan Hsu'
str.match(regex) 

/*
0: "fullName: Alan Hsu"
1: "Alan"
2: "Hsu"
groups:
  firstName: "Alan"
  lastName: "Hsu"
index: 0
input: "fullName: Alan Hsu"
length: 3
*/



// 群組也能搭配 | 簡化重複的規則: /good apple|bad apple/
const regex = /(good|bad) apple/

// 群組也能搭配量詞簡化重複的規則: /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
const regex = /(\d{1,3}\.){3}\d{1,3}/

2. 斷言 (Assertions)

斷言是一種放在程式中的一階邏輯,例如前面介紹的特殊字元 在字串開頭 ^在字串的結尾 $ 就是被歸類在斷言的用法中

當程式執行到斷言的位置時就會執行判斷,若結果為 true 則繼續執行,結果為 false 則中止執行。

// 比對到 \b 的位置時,前後相鄰的字元必須有一個不是文字
regex = /\bJava\b/

// Positive Lookahead: A(?=B) → A 後方的條件要符合 B
// Negative Lookahead: A(?!B) → A 後方的條件不能符合 B
// Positive Lookbehind: (?<=A)B → B 前方的條件要符合 A
// Negative Lookbehind: (?<!A)B → B 前方的條件不能符合 A
// 分析一下要擷取的資料 "前方有一個空格 + 金額 + 後方有一個'元'"
const regex = /(?<=\s)\d+(?=元)/
const str = '數量 2,實付金額 990元'
str.match(regex) 

/*
0: "990"
groups: undefined
index: 10
input: "數量 2,實付金額 990元"
length: 1
*/



七、例子: 表單 Email 欄位輸入值驗證

  • Email 欄位輸入內容條件:
  1. @ 的前後不可為符號
  2. 特殊符號間.-*也不可連續
  3. @ 前面的首字應該不是特殊符號
  4. @ 後面的末字應該是 . 接英文字

同樣的規則通常有多種實現方式。

實現方式 1

let emailRule = /^\w+((\.|-|\*)\w+)*@\w+((\.|-|\*)\w+)*\.[A-Za-z]+$/

實現方式 2

let emailRule = /^\w+((-\w+)|(\.\w+)|(\*\w+))*\@\w+((\.|-|\*)\w+)*\.[A-Za-z]+$/

八、例子: 取得網址 Domain Name

let domainNameRule = /^.+\/\/|(w{3}\.|(.{2,}\.\w+\.\.))|\..{2,}|\/.{1,}\/*.+|\/.+\/?|(?:\..{2,}\/.+|(?:\/+)).+$/g

let domainName = 'https://boison.tw/2022/06/regex-101/'.replace(domainNameRule, "") 
// boison