正規表達式在你看懂各部件之前像亂碼,一旦通了,它就是你最好用的工具之一。這是一份新手也記得住的速查表。
一次真實執行,連群組一起。 我們在正規表達式測試器裡,用
\b(\d{4})-(\d{2})-(\d{2})\b加上g旗標去比對due 2026-07-27, shipped 2026-08-03。它回報 2 match(es),並列出每一個以及它被找到的位置 ——#1 "2026-07-27" @4與#2 "2026-08-03" @24。一個畫面兩個重點:沒有g旗標你只會看到第一個;而那三組括號正是讓你能分別取出年、月、日的原因,不必事後再去剖析比對到的字串。
字元類別
.—— 任意字元(換行除外,除非設s旗標)。\d—— 數字;\D—— 非數字。\w—— 單字字元(字母、數字、底線);\W—— 相反。\s—— 空白;\S—— 非空白。[abc]—— a、b、c 其中之一。[a-z]—— 範圍。[^abc]—— 除了 a、b、c 以外。
量詞(數量)
*—— 零或多個。+—— 一或多個。?—— 零或一個(可選)。{3}—— 剛好 3 個。{2,4}—— 2 到 4 個。{2,}—— 2 個以上。- 在量詞後加
?變成惰性(盡量少配):.+?。
貪婪與非貪婪是量詞裡最花時間的細節,而它只需要一次量測。對 <b>bold</b> and <i>it</i>,pattern <.+> 回傳 1 個比對 —— 它從第一個 < 一路吞到最後一個 >。加一個 ?,<.+?> 回傳 4 個比對:#1"<b>"@0 #2"</b>"@7 #3"<i>"@16 #4"</i>"@21。那個 ? 不改變「什麼能被比對」,只改變 .+ 在停下來之前伸多遠。
錨點與邊界
^—— 字串開頭(設m時為每行開頭)。$—— 結尾。\b—— 單字邊界 —— 很適合整字比對,如\bcat\b。
偏移量才是把「比對到幾個」變成「診斷」的東西。我們的 regex 測試器會列出每一個命中以及它從哪裡開始,所以 \d+ 對 Order 12, item 345, qty 7. 得到 3 個比對,內容是
#1"12"@6 #2"345"@15 #3"7"@24
要讀數字,不只讀字串:第三個命中從 24 開始,這告訴你最後那個 7 是在句號之前被找到的,因此 . 不屬於 \d。加一個邊界就整組都變了 —— \b\w{5}\b 只回傳
#1"Order"@0
一個命中、偏移 0,因為 Order 是唯一一個獨立成詞的五字元單字。
群組與交替
( … )—— 擷取群組;比對到的文字會記成第 1、2⋯ 群組。(?: … )—— 不擷取的群組(只用於結構)。a|b—— 配 a 或 b。
旗標
g—— 找出所有比對,而非只有第一個。i—— 忽略大小寫。m——^與$依每行比對。s—— 讓.也配換行。
幾個實用範式
- 只有數字:
^\d+$ - 簡單日期:
\d{4}-\d{2}-\d{2} - 單字:
\b\w+\b - 粗略的 email:
[^@\s]+@[^@\s]+\.[^@\s]+
(真正的 email 驗證出了名地難 —— 這種粗略樣式用於快速檢查沒問題,但不適合做到完全符合 RFC。)
四個 pattern、同一段沒變過的測試字串(Order 12, item 345, qty 7.),丟進我們自己的 regex 測試器。比對數就是整堂課的重點:決定你拿回多少的是 pattern,不是那段文字。
| Pattern | 比對到的數量 |
|---|---|
\d+ | 3 個比對 |
\d{2,} | 2 個比對 |
\b\w{5}\b | 1 個比對 |
[A-Z]\w+ | 1 個比對 |
省下大把時間的技巧
- 逐步建構。 先寫一小段再加上去,邊看比對即時更新。
- 跳脫特殊字元,若你要它的字面意義:
\.、\?、\(。 - 小心貪婪的
.*—— 它會盡量抓;過頭時改用.*?或更精確的類別。 - 用真實資料測試,包括你預期會失敗的邊界情況。
學最快的方法是邊打邊看比對反白。在免費的正規表達式測試器即時試 —— 它會反白每個比對並列出擷取群組,全程在你的瀏覽器。
進階一點:環顧與具名群組
基礎通了之後,有兩組功能能解鎖很多用法:
- 正向環顧(lookahead)
(?=…)與負向環顧(?!…):只有在「後面接的東西」符合(或不符合)時才比對這個位置,而且不會把它吃掉。\d(?=px)會比對「後面接著px」的數字。 - 後顧(lookbehind)
(?<=…)與(?<!…):對「前面的東西」做同樣的事。如今支援度很廣(JavaScript 自 ES2018 起,PCRE、.NET 也支援)。 - 具名群組
(?<year>\d{4}):比起數\1、\2,可讀性高得多。可用\k<year>或在取代字串裡以名稱引用。
常見錯誤
- 中括號
[…]內,特殊字元會失效。 在字元類別裡,.、*、(都是字面意義;只有^(在開頭)、-(在字元之間)、]與\仍有特殊作用。所以[.?]就是比對一個句點或問號。 \d不一定是 Unicode。 在 JavaScript,\d就等於[0-9];有些引擎會把它擴及其他語系的數字。想要純 ASCII,就寫[0-9]。- 災難性回溯。 對重疊的文字用巢狀量詞——例如用
(a+)+$去比對一長串不符合的字串——可能讓引擎卡死。改寫掉這種模稜兩可。
最花時間的錯,是以為量詞會停在「你會停下來讀」的地方。對 <b>bold</b> and <i>it</i>,非貪婪的 <.+?> 回傳
#1"<b>"@0 #2"</b>"@7 #3"<i>"@16 #4"</i>"@21
四個命中,分別在 0、7、16、21 —— 每一個標籤,而中間的內容一個都沒有。貪婪的 <.+> 回傳一個命中、在 0,涵蓋整個字串。同樣的字元、同樣的輸入,唯一的差別是一個 ?。
繼續學下去
值得「解密」的精簡語法不只正則——如果你會排定工作,那種「乍看像亂碼、通了就懂」的感覺,在 Cron 表達式上一模一樣。