正則表達式完整教學:50 個真實案例 + 線上測試器使用法
寫給:每次 grep 都查 cheatsheet 的工程師、想 validate 表單欄位的前端、寫爬蟲的資料工程師,以及永遠對
\w+跟\w*哪個是貪婪不確定的所有人。
正則表達式(Regular Expression,簡稱 regex 或 regexp)是描述字串模式的小型語言。1956 年由數學家 Stephen Kleene 提出,1970 年代被 ed / grep / sed 採用,至今仍是文字處理最強的工具之一。但 regex 的學習曲線陡峭——本文用 15 分鐘整理 5 個核心觀念、50 個實戰範例,並示範如何用 SuperTools 線上 Regex 測試器互動式練習。
1. 正則表達式是什麼
Regex 是一種用字串描述「字串模式」的迷你 DSL。例如 \d{3}-\d{4} 描述「三個數字、一個減號、四個數字」這個模式,可以一行匹配電話號碼。
主流引擎可分三大家族:
| 家族 | 代表實作 | 特色 |
|---|---|---|
| POSIX BRE/ERE | grep, sed | 最古早、語法精簡 |
| PCRE / PCRE2 | PHP, nginx, Python regex lib |
功能最完整 |
| ECMAScript | JavaScript | 瀏覽器原生,部分功能缺失 |
Python re 介於 PCRE 與 POSIX 之間,多數 PCRE 高階特性(lookbehind 變長度、recursive pattern)要用第三方 regex 套件。
regex 適合做的事:
- 文字搜尋 / 取代
- 表單驗證
- log 解析
- 簡單字串切割
不適合:解析巢狀結構(HTML、JSON、code)—— 這些請用 parser。
2. 5 個核心觀念
2.1 字元類別(character class)
| Pattern | 意義 |
|---|---|
. |
任意字元(不含換行) |
\d |
數字 [0-9] |
\D |
非數字 |
\w |
字母+數字+底線 [A-Za-z0-9_] |
\W |
非 word 字元 |
\s |
空白(含 tab、換行) |
\S |
非空白 |
[abc] |
a、b、c 任一個 |
[^abc] |
不是 a、b、c |
[a-z] |
a 到 z |
踩坑:\w 在 Python 預設含 Unicode 字母(中文),JS 預設不含。要跨語言一致請用 [A-Za-z0-9_]。
2.2 量詞(quantifier)
| Pattern | 意義 |
|---|---|
* |
0 次以上 |
+ |
1 次以上 |
? |
0 或 1 次 |
{n} |
剛好 n 次 |
{n,} |
n 次以上 |
{n,m} |
n 到 m 次 |
貪婪 vs 懶惰:預設貪婪(盡量多吃),加 ? 變懶惰(盡量少吃):
import re
text = "<b>foo</b><b>bar</b>"
re.findall(r"<b>.*</b>", text) # ['<b>foo</b><b>bar</b>'] 貪婪
re.findall(r"<b>.*?</b>", text) # ['<b>foo</b>', '<b>bar</b>'] 懶惰
2.3 錨點(anchor)與邊界
| Pattern | 意義 |
|---|---|
^ |
行首 |
$ |
行尾 |
\b |
word 邊界 |
\B |
非 word 邊界 |
\A |
字串開頭 |
\z |
字串結尾 |
注意 ^$ 跟 \A\z 差別:multiline flag 開啟時,^$ 配對「每一行」開頭與結尾;\A\z 永遠是整個字串。
re.findall(r"^hello", "hello\nhello", re.MULTILINE) # ['hello', 'hello']
re.findall(r"\Ahello", "hello\nhello") # ['hello']
2.4 群組與捕獲
# 捕獲群組
m = re.match(r"(\w+)@(\w+\.\w+)", "alice@example.com")
m.group(0) # 'alice@example.com' 整段
m.group(1) # 'alice'
m.group(2) # 'example.com'
# 命名群組
m = re.match(r"(?P<user>\w+)@(?P<domain>\w+\.\w+)", "alice@example.com")
m.group("user") # 'alice'
m.group("domain") # 'example.com'
# 非捕獲群組(只分組不抓內容)
re.findall(r"(?:abc)+", "abcabcabc") # ['abcabcabc']
2.5 前後查找(lookaround)
| Pattern | 意義 |
|---|---|
(?=X) |
後面是 X(不消耗字元) |
(?!X) |
後面不是 X |
(?<=X) |
前面是 X |
(?<!X) |
前面不是 X |
用途:抓取符合上下文但不想包進結果的字串。
# 抓 $ 後的數字(不包 $)
re.findall(r"(?<=\$)\d+", "price $100 and $200")
# ['100', '200']
# 抓不是後接 .png 的檔名
re.findall(r"\w+(?!\.png)", "a.png b.jpg c.png")
3. 實戰範例
下面 50 個 pattern 涵蓋常見場景,可以直接複製使用。建議搭配 SuperTools 線上測試器逐一試跑。
範例 1-10:表單驗證
# 1. Email(簡化版,RFC 5322 完整版超過 6000 字元)
^[\w.+-]+@[\w-]+(\.[\w-]+)+$
# 2. URL
^https?:\/\/[\w.-]+(:\d+)?(\/[\w._~:/?#[\]@!$&'()*+,;=-]*)?$
# 3. IPv4
^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$
# 4. IPv6(簡化版)
^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$
# 5. 台灣手機
^09\d{8}$
# 6. 台灣市話(含區碼)
^0[2-9]-?\d{6,8}$
# 7. 身分證字號
^[A-Z][12]\d{8}$
# 8. 信用卡(VISA / Master / JCB / Amex 通用)
^(?:4\d{15}|5[1-5]\d{14}|3[47]\d{13}|35\d{14})$
# 9. 強密碼(8+ 字、含大小寫、數字、特殊字元)
^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[\W_]).{8,}$
# 10. 統一發票
^[A-Z]{2}-?\d{8}$
範例 11-20:日期時間
# 11. YYYY-MM-DD
^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$
# 12. HH:MM 24 小時
^([01]\d|2[0-3]):[0-5]\d$
# 13. ISO 8601(YYYY-MM-DDTHH:MM:SSZ)
^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})$
# 14. 民國年(112-05-18)
^1\d{2}-\d{2}-\d{2}$
# 15. Unix timestamp(10 位)
^\d{10}$
# 16. 月份英文簡寫
^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)$
# 17. 星期英文
^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)(day)?$
# 18. 12 小時制(HH:MM AM/PM)
^(0?[1-9]|1[0-2]):[0-5]\d\s?(AM|PM|am|pm)$
# 19. 持續時間(HH:MM:SS)
^\d{2}:\d{2}:\d{2}$
# 20. 月日(MM/DD)
^(0[1-9]|1[0-2])/(0[1-9]|[12]\d|3[01])$
範例 21-30:HTML / Markdown
# 21. HTML 標籤(簡單版)
<(\w+)([^>]*)>(.*?)</\1>
# 22. img src
<img[^>]+src=["']([^"']+)["']
# 23. 超連結 href
<a[^>]+href=["']([^"']+)["']
# 24. Markdown 連結
\[([^\]]+)\]\(([^)]+)\)
# 25. Markdown 圖片
!\[([^\]]*)\]\(([^)]+)\)
# 26. Markdown H1
^#\s+(.+)$
# 27. Markdown 程式碼塊
```(\w*)\n([\s\S]*?)```
# 28. HTML 註解
<!--[\s\S]*?-->
# 29. CSS 選擇器(簡單版)
^[.#]?[a-zA-Z][\w-]*(\s*[>+~]\s*[.#]?[a-zA-Z][\w-]*)*$
# 30. HEX 顏色
^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
範例 31-40:log 解析
# 31. Apache access log(commonl format)
^(\S+) \S+ \S+ \[([^\]]+)\] "(\S+) ([^"]+) ([^"]+)" (\d+) (\d+|-)$
# 32. nginx error log
^(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) \[(\w+)\] (.+)$
# 33. JSON log line
^\{.*"level":"(\w+)".*\}$
# 34. python traceback file:line
File "([^"]+)", line (\d+), in (\w+)
# 35. log 中的 UUID
\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b
# 36. 錯誤等級
\b(DEBUG|INFO|WARN|WARNING|ERROR|FATAL|CRITICAL)\b
# 37. 抓出 stack trace 第一行
^Exception in (\S+): (.+)$
# 38. log 中的 IP + status
^(\S+) .+ "(\d{3})"
# 39. duration 毫秒
(\d+(\.\d+)?)\s?ms
# 40. 帶 timestamp 的多行 log 切割
^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}
範例 41-50:資料清洗
# 41. 移除 HTML 標籤
<[^>]+>
# 42. 多重空白合一
\s+
# 43. 開頭結尾空白
^\s+|\s+$
# 44. 全形空白
# 45. 中文(CJK 統一漢字)
[一-鿿]
# 46. emoji(簡化版)
[\u{1f300}-\u{1f9ff}]
# 47. 中文標點符號
[ -〿-]
# 48. base64 字串(含 padding)
^[A-Za-z0-9+/]+={0,2}$
# 49. JWT
^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
# 50. 重複空行壓成一行
\n{2,}
4. 進階技巧
4.1 反向引用(backreference)
群組捕獲後可以用 \1、\2 引用:
# 找出重複的單字
re.findall(r"\b(\w+)\s+\1\b", "the the quick brown fox fox")
# ['the', 'fox']
# 取代 HTML 標籤包裹的文字
re.sub(r"<(\w+)>(.*?)</\1>", r"[\2]", "<b>hello</b>")
# '[hello]'
4.2 條件式(PCRE)
# 如果群組 1 有匹配,就要求 X,否則要求 Y
(?(1)X|Y)
JavaScript 不支援,Python re 也不支援,要用 regex lib。
4.3 原子群組 + 占有量詞(防回溯災難)
某些 regex 在惡意輸入下會 catastrophic backtracking 卡死 CPU:
# 災難 pattern
^(a+)+$
對輸入 "aaaaaaaaaaaaaaaaaaaa!" 會跑數秒。改用原子群組:
^(?>a+)+$
或用占有量詞 a++(不回溯)。Java、PCRE 支援;JS、Python 不支援。
4.4 多行 flag 與 dot-all
| Flag | 意義 |
|---|---|
i |
忽略大小寫 |
m |
multiline(^$ 配對每行) |
s |
dot-all(. 包含換行) |
x |
extended(忽略 whitespace 與 # 註解) |
g |
global(全部匹配,僅 JS) |
re.findall(r"foo.bar", "foo\nbar") # []
re.findall(r"foo.bar", "foo\nbar", re.DOTALL) # ['foo\nbar']
4.5 Unicode property(pcre / python regex)
\p{L} # 任何字母(含中文、日文、阿拉伯文)
\p{Han} # 漢字
\p{N} # 數字
\p{P} # 標點
JS ES2018+ 用 u flag 後支援 \p{L}。
5. 常見錯誤與解法
錯誤 1:用 regex 解析 HTML
症狀:抓 <a> 連結時錯抓註解 / 字串內容。
根因:HTML 是巢狀結構,不是 regular language。
解法:用 BeautifulSoup / Cheerio / lxml 等 HTML parser。
錯誤 2:catastrophic backtracking 卡死
症狀:CPU 100%,特定輸入 regex 跑數秒不停。
根因:巢狀量詞如 (a+)+。
解法:簡化 pattern、使用原子群組、改用線性時間引擎(Google RE2)。
錯誤 3:忘記跳脫特殊字元
症狀:想匹配 1.2.3 卻匹配到任何三字元。
根因:. 是 metacharacter,要用 \.。
解法:把字面值 string 過 re.escape() 再嵌入 pattern。
import re
target = "1.2.3"
pattern = re.escape(target) # '1\\.2\\.3'
錯誤 4:JS 跟 Python 結果不同
症狀:同一個 regex 在 Python 過、在瀏覽器不過。 根因:lookbehind 變長度、命名群組語法、Unicode 處理三大引擎不同。 解法:跨語言部署的 regex 限定用 ECMAScript 共用子集;複雜邏輯放後端 Python / Go。
錯誤 5:貪婪 vs 懶惰搞錯
症狀:抓 <b>foo</b><b>bar</b> 時整段都被抓。
根因:.* 是貪婪。
解法:改 .*? 懶惰,或用 [^<]+ 限制字元集。
6. 用 SuperTools 線上版完成相同任務
不想開 Python 互動式 shell?開 SuperTools Regex 測試器:
- 即時 highlight:輸入 pattern 立刻在 test string 上 highlight 所有 match
- 群組 panel:右側顯示每個捕獲群組的內容
- flag toggle:i / m / s / g / u 一鍵切換
- 替換預覽:輸入 replacement template 看取代結果
- explain mode:把 pattern 拆解成自然語言描述(適合學習)
- 多語言切換:JavaScript / Python / Go 三引擎隨選
工具完全 client-side(瀏覽器原生 RegExp + WASM Python re),測試字串不會上傳。
實際使用流程:
- 從 log / API 複製需要解析的字串
- 開 supertools.tw/dev/regex 貼到右側 test string
- 在左側 pattern 區邊寫邊看 highlight
- 寫對後點「Copy as Python / JS」貼回程式碼
立即使用 SuperTools Regex 測試器:https://supertools.tw/dev/regex
7. 其他語言實作
Python
import re
re.match(r"^\d+$", "123") # 從頭開始匹配
re.search(r"\d+", "abc123def") # 找第一個
re.findall(r"\d+", "1 2 3") # ['1', '2', '3']
re.sub(r"\d", "*", "a1b2c3") # 'a*b*c*'
JavaScript
"abc123".match(/\d+/); // ['123']
"a1b2".replace(/\d/g, "*"); // 'a*b*'
/^[a-z]+$/i.test("Hello"); // true
Go
import "regexp"
r := regexp.MustCompile(`\d+`)
r.FindAllString("1 2 3", -1) // ["1", "2", "3"]
r.ReplaceAllString("a1b2", "*") // "a*b*"
Rust
use regex::Regex;
let re = Regex::new(r"\d+").unwrap();
for m in re.find_iter("1 2 3") { println!("{}", m.as_str()); }
Bash
echo "abc123" | grep -oE "[0-9]+" # 123
echo "abc123" | sed -E 's/[0-9]+/X/' # abcX
8. FAQ
Q1:要不要學 PCRE 跟 POSIX 全部? 不用。先學 PCRE / ECMAScript 共通子集,覆蓋 90% 場景。
Q2:regex 跟 glob 差別?
glob 是 shell 萬用字元(*.txt),語意更簡單;regex 是完整模式語言。
Q3:catastrophic backtracking 怎麼預防? 避免巢狀量詞、用原子群組、用 RE2 引擎、給 regex 設 timeout。
Q4:要學 lookahead 嗎? 強烈建議。它是密碼強度、token 驗證等場景的關鍵。
Q5:能用 regex 做 lexer 嗎? 可以,但只適合簡單語法。複雜語言請用 ANTLR / yacc / 手寫 parser。
9. 實戰案例研究
案例 1:Cloudflare 2019 全球當機 27 分鐘
2019 年 7 月 2 日,Cloudflare 推送一條新 WAF 規則含特定 regex:
(?:(?:\"|'|\]|\}|\\|\d|(?:nan|infinity|true|false|null|undefined|symbol|math)|\`|\-|\+)+[)]*;?((?:\s|-|~|!|{}|\|\||\+)*.*(?:.*=.*)))
這條 pattern 對特定 user request 造成 catastrophic backtracking,全球 edge server CPU 飆到 100%,27 分鐘全球無法存取網站,連 Cloudflare 自己網站都掛。
事後檢討:
- 全球 CDN 任何 regex 改動必經多輪壓測
- 改用 Rust RE2(線性時間引擎)
- 規則上線前 staging 7 天
教訓:regex 在 production 系統有真實 SLO 影響。
案例 2:log 解析從 20 分鐘 → 2 秒
某團隊用 Python 解析 1GB nginx log 提取所有 IP,原本:
for line in open("access.log"):
ip = re.match(r"^(\d+\.\d+\.\d+\.\d+)", line).group(1)
每行 re.compile 一次(其實 Python 有 cache,但仍 overhead)+ 沒用 re.compile 預編。
優化:
import re
IP_RE = re.compile(rb"^(\d+\.\d+\.\d+\.\d+)") # 預編 + binary mode
with open("access.log", "rb") as f:
ips = [m.group(1) for line in f if (m := IP_RE.match(line))]
加 binary mode(不 decode)+ 預編 + walrus operator,從 20 分鐘 → 2 秒(600x 加速)。
案例 3:表單驗證從 50 個 regex 變 1 個 JSON Schema
某入會表單原本有 20 個欄位,每個欄位有 1-3 個 regex(email、phone、id_card 等),總共 50+ regex 散在前後端。同步維護地獄。
改用 JSON Schema:
{
"type": "object",
"properties": {
"email": {"type": "string", "format": "email"},
"phone": {"type": "string", "pattern": "^09\\d{8}$"},
"id_card": {"type": "string", "pattern": "^[A-Z][12]\\d{8}$"}
}
}
前後端共用同一 schema:
- 前端
ajvvalidate - 後端
jsonschemavalidate
新增欄位只改 schema、不改 code。
10. 結論 + 延伸閱讀
正則表達式是文字處理的瑞士刀。本文 50 個範例涵蓋 90% 日常需求,背後 5 個核心觀念(字元類別、量詞、錨點、群組、lookaround)熟練後幾乎所有 pattern 都能拆解。下次寫 validation 或 log 解析時,先到 SuperTools 線上測試器把 pattern 練到綠燈再貼進程式碼,省下無數 debug 時間。三條黃金法則:生產 regex 必先壓測防回溯、熱迴圈用 re.compile 預編、多欄位 validation 改 JSON Schema。
延伸閱讀: