# [ 正規表達式 Regex ] (基礎篇) 學習歷程筆記 使用程式語言:python 使⽤套件:re 作者:允安B ## 甚麼是正規表達式? 簡單來說它是一種搜索器,可以幫你在"處理字串"的工作上省去很多時間。你可以自訂任何規則,像是幾個r後面要接幾個t阿各種複雜的規則都可以用正規表達式呈現,以實用性上來說,你可以寫一個規則,用於匹配一個gmail信箱有沒有符合規格,或是寫一個規則,將字串內的所有符合規則的段落都替換掉等等。 # 正規表達式基本規則 **底層邏輯是這樣的** 要寫一個規則,最最基本就是直接用字元照抄,例如`5000`可以匹配到5000(廢話),但是只有這樣當然無法廣泛應用,所以還有其他工具可以讓你塑造出最完美的規則。 **甚麼是"匹配"?** 在這篇筆記中常常會出現匹配一詞,他指的是 **你的規則** 與 **目標內容** 相互吻合時則稱為匹配成功。有點像是一個嫌疑犯被無人機辨識出來會顯示匹配成功...... ## `.` 這非常重要,`.`可以匹配除了`\n`換行符的任何字元,常用在湊數量上 例如我今天要在一個有2000~3000所有正整數的名單當中找出百位數為5的所有數字則可訂規則如: ``` .5.. ``` ## `[]` 這可以自訂集合,在中括號內的所有字元都可以符合這一格的條件,承上個舉例,我今天不只想要百位數為5,我還想要只有偶數,則可訂規則如 ``` .5.[24680] ``` 另外如果你要一個範圍可以寫`[a-z]` 就不用a~z慢慢打,同理`[0-9]`、`[A-F]`...等等 ## `[^ ]` 與[ ]不同之處在於他是反向的條件,只要符合就不給過,在承上舉例,我今天再增加一個規則是十位數字不可以大於3,也就是≤3的都符合,則可訂規則如 ``` .5[^456789][24680] ``` 第一個規則為任意字元,只是為了讓第二格對齊百位數 第二個規則為綁定 5,只讓百位數為5的資料通過 第三個規則為大於3的數字不通過,讓十位數太大的資料被攔截 第四個規則為24680的其中之一都可通過,讓個位數為偶數(個位數是偶數則該資料為偶數) ## `\d` 此為預設幫你定義好的字符集,用於匹配數字,等同於`[0123456789]`,承上舉例若資料堆中混入英文為開頭的資料例如"S522"或是開頭為空白的三位數" 580"都會被成功匹配到,若想改善則可修改規則如 ``` \d5[^456789][24680] ``` 附帶一提,\D為非數字的字符集,等同於`[^0123456789]`或`[^\d]` ## `\s` 可以成功配對到任何空白字符,等同於`[ \t\n\r\f\v]` 底下補充,知道就好 `\t` 製表符 ;`\n` 換行符 ;`\r` enter ;`\f` 換頁符 ;`\v`垂直製表符 `\w`等同於`[a-zA-Z0-9_]`; `\W`等同於`[^a-zA-Z0-9_]` # 數量概念 前面講到字符集都是單一字符,但如果今天想找出連續100個數字的話,不可能打100個\d那真的太累了,所以可以用到這類的規則,這邊的規則都是指前一個字符或匹配符。 ## `{m}` m是變數可以自行調整,代表連續m個字符,例如100個數字則可定義規則如 ``` \d{100} ``` 就是100個連續數字的意思 ## `{n,m}` n~m個字符都可通過,例如規則`hel{3,6}o` 可以匹配`helllo`、`hellllo`、`helllllo`、`hellllllo`等有3~6個`l`的字串 ## `{n,}` 只要超過n個字符連續則可匹配,例如規則`py{2,}`可以匹配py後面接2個或兩個以上到無限個y都可以匹配成功(當然電腦不可能裝得下無限個y,但不管有多少都會被匹配成功) ## `?` 前一個字符出現0或1次,可以想像成朋友聚餐不知道某人會不會出現所以打個問號 舉例來說手機電話號碼的中間有人會加入`-`,若想匹配手機電話號碼則可定義規則如 ``` \d{4}-?\d{3}-?\d{3} ``` 說明:4個數字,3個數字,3個數字中間如果加入`-`也能成功匹配 成功匹配舉例:`0988123546` `0911-123-789` ## `+` 前一個字符出現一次或一次以上,可以想像成常常看到的`18+`也就是十八歲以上的概念。他只要求最低下限,但不限制上限。其實等同於`{1,}`但想想都知道`+`比較快。這個很直觀也比較簡單,所以就不舉例了。 ## `*` 這也很常用,它的功能是匹配0次或多次字符的出現,很FREE是它的特性,也因為他可以匹配0~無限這個超大的範圍,所以任何不確定因素都可以由他來擺平。 `.*`是最萬能的組合,只要在同一行內全部都可以吃得下(匹配成功),俗稱大胃王XD # 常見工具或概念補充 只有上面還不夠,還需要一些好朋友幫你解決某些特定問題。 ## `()` 群組的概念,把一坨東西打包起來,在後面的常用函式以及進階篇都可以發現出場率很高。 舉例來說我想要抓出`@name`後面的`name`,你可能會想說就直接寫`@[a-z0-9]+` 或`@\w+`但是這樣回回傳給你有包含`@`的資料,這時可以寫`@(\w+)`,那麼再用`re.findall`時就會幫你把前面的`@`去掉。 ## `|` 或,也就是or的概念,將左右的規則合成成一個規則,其中一個滿足即可匹配,例如我想找到`!!abcde`以及`!!12345`則可定義規則為`!!(abcde|12345)`來同時匹配到兩個目標 ## `\` 後面的特殊字符變回基本字符不會被誤會。 舉例來說如果我今天想要在一篇很長的文章裡面找出所有的`a.m.`或`p.m.`,那你可能要寫`[ap].m.`或包含大小寫可能性的`[apAP].[mM].`,但你會發現連`aims`這種因為`.`萬用符符合而被一起匹配出來的東西,所以這時需要用到`\`放在`.`前面變成`[apAP]\.[mM]\.`則可讓萬用符`.`轉回點`.`。同理`\\`代表單一反斜線`\`字符 ## 貪婪與非貪婪模式 貪婪模式:會想盡辦法把所有符合規則的字符都吃進來 非貪婪模式:懶惰一點,吃到可以停止就不可能再吃任何東西 這樣講很抽象,舉例來說如果今天想把字串`for i in range(10)`分別切出`for`、`i`、`in` 等單字,那你可能會寫`.+ `,但是實際運行會發現因為空白也符合`.`的定義所以你會得到`for i in `這樣得東西,如圖 ![image](https://hackmd.io/_uploads/HkTFPiPJle.png) 輸出 ![image](https://hackmd.io/_uploads/Hyj9vjwJee.png) 那想改善這個問題則可通過非貪婪模式的設定來達成分開輸出效果 ![image](https://hackmd.io/_uploads/Bk8etjDJeg.png) 輸出 ![image](https://hackmd.io/_uploads/rJ6ZFjPkgg.png) 阿沒錯就是加了一個`?`而已,相同方式也可以用在`*?`上。 在python中預設為貪婪模式。 # 正則表達式常用函式 看到這裡你也許有對正規表達式有一些理解,那在程式設計要如何運用呢? 以下是我整理的在python裡實際運用的方式。 ## 1. re.match() # 用於從字串的開頭開始匹配正則表達式。如果匹配成功,返回一個匹配對象;否則,返回 None。 import re pattern = r'Hello' text = 'Hello World!' match = re.match(pattern, text) if match: print("Match found:", match.group()) else: print("No match") >>> Match found: Hello ## 2. re.search() # 用於在整個字串中搜索匹配正則表達式的第一個位置。如果匹配成功,返回一個匹配對象;否則,返回 None。 import re pattern = r'World' text = 'Hello World!' match = re.search(pattern, text) if match: print("Match found:", match.group()) else: print("No match") >>> Match found: World ## 3. re.findall() # 用於在整個字串中搜索所有匹配正則表達式的子串,並以列表的形式返回。 import re pattern = r'\d+' text = 'There are 123 apples and 456 oranges.' matches = re.findall(pattern, text) print("Matches found:", matches) >>> Matches found: ['123', '456'] ## 4. re.finditer() # 用於在整個字串中搜索所有匹配正則表達式的子串,並以迭代器的形式返回每個匹配對象。 import re pattern = r'\d+' text = 'There are 123 apples and 456 oranges.' matches = re.finditer(pattern, text) for match in matches: print("Match found:", match.group()) >>> Match found: 123 Match found: 456 ## 5. re.sub() # 用於替換字串中所有匹配正則表達式的子串。 import re pattern = r'\d+' text = 'There are 123 apples and 456 oranges.' replacement = '#' result = re.sub(pattern, replacement, text) print("Result:", result) >>> Result: There are # apples and # oranges. ## 6. re.split() # 用於根據匹配正則表達式的子串來分割字串,並以列表的形式返回分割後的子串。 import re pattern = r'\s+' text = 'Hello World! How are you?' result = re.split(pattern, text) print("Result:", result) >>> Result: ['Hello', 'World!', 'How', 'are', 'you?'] ## 7. re.compile() # 用於編譯正則表達式,返回一個正則表達式對象,該對象可以重複使用。 import re pattern = re.compile(r'\d+') text = 'There are 123 apples and 456 oranges.' matches = pattern.findall(text) print("Matches found:", matches) >>> Matches found: ['123', '456'] ## 8. re.fullmatch() # 用於匹配整個字串,如果整個字串匹配正則表達式,則返回一個匹配對象;否則,返回 None。 import re pattern = r'\d+' text = '123456' match = re.fullmatch(pattern, text) if match: print("Full match found:", match.group()) else: print("No full match") >>> Full match found: 123456 ## 9. re.escape() # 用於對字串中的所有非字母數字字符進行轉義,這樣它們在正則表達式中可以被當作字面量字符。 import re text = 'Hello. How are you?' escaped_text = re.escape(text) print("Escaped text:", escaped_text) >>> Escaped text: Hello\. How are you\? ## 10. re.subn() # 類似於 re.sub(),但返回一個元組,包含新字串和替換次數。 import re pattern = r'\d+' text = 'There are 123 apples and 456 oranges.' replacement = '#' result, count = re.subn(pattern, replacement, text) print("Result:", result) print("Number of replacements:", count) >>> Result: There are # apples and # oranges. Number of replacements: 2 ## 11. re.IGNORECASE (re.I) # 用於忽略大小寫進行匹配。 import re pattern = r'hello' text = 'Hello World!' match = re.search(pattern, text, re.IGNORECASE) if match: print("Match found:", match.group()) else: print("No match") >>> Match found: Hello ## 12. re.MULTILINE (re.M) # 用於多行匹配,影響 ^ 和 $ 的行為。 import re pattern = r'^Hello' text = 'Hello World!\nHello Python!' matches = re.findall(pattern, text, re.MULTILINE) print("Matches found:", matches) >>> Matches found: ['Hello', 'Hello'] ## 13. re.DOTALL (re.S) # 用於使 . 匹配包括換行符在內的所有字符。 import re pattern = r'Hello.*World' text = 'Hello\nWorld' match = re.search(pattern, text, re.DOTALL) if match: print("Match found:", match.group()) else: print("No match") >>> Match found: Hello\nWorld ## 14. re.VERBOSE (re.X) # 用於允許在正則表達式中使用空白符和註釋,以提高可讀性。 import re pattern = r''' \d+ # 數字 \s+ # 空白 \w+ # 單詞 ''' text = '123 abc' match = re.search(pattern, text, re.VERBOSE) if match: print("Match found:", match.group()) else: print("No match") >>> Match found: 123 abc ## 15. re.purge() # 用於清除正則表達式的緩存。 import re re.purge() print("Regular expression cache cleared.") >>> Regular expression cache cleared. # 學完之後 沒學過正規表達式的時候會覺得它看起來可怕,我剛學完之後唯一的感覺就是 **能用,但不多** 我在寫的程式也用不到很多,啊我學這個要做甚麼?好問題,拿來玩。所以我當時找了一些測試實力的遊戲或可以加強我對於表達式理解的網站,以下提供給大家。 ## regex101 很好用,當你看不懂一串正規表達式時可以放進來他會用顏色區分段落,一目瞭然,也有一個一個觀念的學習,讓你透過實際操作一步一步學會正規表達式 {%preview https://regex101.com/ %} ## RegexOne 這個比較像是給初學者練習+學習用的,一關一關破完對於正規表達的基礎有一定的幫助 {%preview https://regexone.com/ %} ![image](https://hackmd.io/_uploads/rJUr7q81le.png) ## Regex Crossword 概念相同但可以從簡單慢慢增加難度,後面還可以玩其他人做的puzzle {%preview https://regexcrossword.com/ %} ![image](https://hackmd.io/_uploads/By4q-5L1lx.png) ## RegEx-Crossword 這是一個網頁遊戲,與數獨概念相同,每一格需要同時滿足三個規則,最後讓每一個規則都正確且完全匹配,有好幾關,難度頗高可以挑戰看看。 {%preview https://jimbly.github.io/regex-crossword/ %} ![image](https://hackmd.io/_uploads/B1l-ecLJxg.png)