正規表達式測試工具
預期結果: 會找到兩個日期:2026-10-02 與 2026-11-15。啟用取代後結果為 Releases: 02/10/2026, 15/11/2026.
限制: 使用 JavaScript RegExp,而非 PCRE。此模式只比對日期格式,不驗證日期是否存在。Worker 執行時間上限為 1.5 秒。
您輸入的內容不會被儲存、記錄或分享。
輸入樣式後,符合的內容會即時標示,並顯示擷取群組與位置。比對在背景執行緒中進行,因此即使遇到災難性回溯,頁面也不會凍結。
使用方式
- 輸入樣式並勾選需要的旗標。
- 貼上要測試的文字,符合處會立即標示。
- 勾選「取代」即可用 $1 或 $<name> 預覽取代結果。
正規表示式做不到的事
正規表示式比對的是模式,而不是結構。它無法記住自己在某個東西裡面有多深,這表示任何可以任意巢狀的東西,都確實超出它能表達的範圍:成對的括號、巢狀的引號、HTML、JSON、原始碼。
看似能處理這些內容的模式,其實只是比對到常見情況,其他情況則會失敗,而且通常是無聲無息地失敗。屬性裡出現 > 之前都正常的 HTML 標籤模式;會把字串常值中間吃掉的註解移除器;遇到第一個被引號包住的逗號就出錯的 CSV 分割器。每一個都是幾個月後才在別人的資料上爆發的錯誤。
經驗法則是:如果你要比對的東西可以包含自己,你需要的是解析器。正規表示式在它下面那一層表現出色:驗證欄位、從已知的日誌格式中擷取值、在整個程式碼庫中搜尋與取代、用不會出現在各部分內部的分隔符號切割。
貪婪、懶惰,以及回溯從何而來
量詞預設是貪婪的:.* 會盡可能多取,再一次退回一個字元,直到其餘模式能夠符合。所以 <.+> 套用在 <b>text</b> 上,會比對整個字串而不是第一個標籤,因為貪婪的 .+ 吞下了一切,而最後的 > 由輸入中的最後一個 > 滿足。
加上問號會讓量詞變成懶惰:<.+?> 盡可能少取,只比對到 <b>。懶惰通常才是大家想要的,但字元類別通常比兩者都好:<[^>]+> 直接說出你真正要的東西,完全不可能跨過標籤邊界,也不會回溯。
最後這點正是實務上該在意的原因。回溯是讓模式變慢的元凶,而把一個量詞放進另一個量詞裡,例如 (a+)+、(\s*\w+)*,會讓引擎探索的路徑數量呈指數成長。遇到幾乎符合的輸入,這種模式可能跑上好幾分鐘。本測試工具在 worker 中執行比對,並在一秒半後強制終止,正是因為不小心寫出這種模式再平常不過。
會讓你吃虧的方言差異
這裡使用的是瀏覽器的引擎,所以結果與 JavaScript 完全一致,但不一定與你要貼上模式的那個語言一致。
具名群組在 JavaScript、.NET 與新版 PCRE 中寫作 (?<name>...),但 Python 也接受 (?P<name>...),而較舊的工具只看得懂後者。後顧斷言在目前的 JavaScript、.NET 與 PCRE 中可用,但在 Python 中必須是固定長度,在 Go 中則完全不存在。Go 的 RE2 與 Rust 的 regex crate 刻意省略了反向參照與環視,這才讓它們能保證線性時間;依賴其中任何一項的模式根本無法編譯。
簡寫類別的意義也會改變。在 JavaScript 中 \d 只代表 ASCII 數字;在 .NET 中預設會比對任何文字系統的數字;在 Python 中則取決於模式是 str 還是 bytes。而 JavaScript 的 u 旗標,是讓 . 與量詞以完整碼位而非碼元運作的關鍵;沒有它,模式遇到表情符號或任何基本多文種平面以外的字元就會出錯。
重要的模式在上線前,請先在你自己的執行環境中測試。
寫出日後仍讀得懂的模式
加上錨點。用來驗證欄位卻沒有 ^ 和 $ 的模式,會很樂意比對子字串,所以沒有錨點的電子郵件模式,會接受任何裡面藏著有效地址的字串。這是驗證被繞過的常見原因。
兩者都可行時,優先使用字元類別而非選擇(|):[abc] 比 (a|b|c) 更清楚也更快,而 [^,]+ 能確實表達「不跨過逗號」,.+? 只能近似做到。群組請命名而不是用數的:有人在前面插入一個群組時,(?<year>...) 依然正確,$3 則不然。
接著測試反例。大多數模式的寫法,是不斷嘗試應該符合的輸入直到成功,結果幾乎一定太過寬鬆。更有用的做法是把絕對不能符合的輸入貼進來,確認它們真的不符合:格式錯誤的地址、內含分隔符號的值、幾乎但不完全符合預期日誌格式的那一行。
逐段讀懂一個真實的樣式
拿一個常見記錄檔行的樣式來看:^(\S+) \S+ \S+ \[([^\]]+)\] "(\w+) ([^"]*)" (\d{3}) (\d+)$。它看起來很密,拆開來卻很乾淨。開頭的插入符號把比對錨定在行首。(\S+) 擷取一串非空白字元,也就是用戶端位址,後面兩個沒有擷取的 \S+ 則跳過沒人在用的 identity 與 user 欄位。
\[([^\]]+)\] 擷取時間戳記:先是一個跳脫的方括號,接著是所有不是右方括號的字元,最後是右方括號。讓這段夠穩固的關鍵在於反向字元類別,因為它不會像 .+ 那樣衝過欄位的結尾。引號包起來的請求用的是同一招,只是把括號換成引號,並拆成方法與路徑兩部分。
(\d{3}) 為狀態碼擷取剛好三位數字,(\d+) 擷取位元組數,最後用錢字號錨定行尾。這個心得可以推廣:用反向字元類別和明確次數寫出來的樣式,比用點號和惰性量詞寫出來的同一個樣式更好讀,也跑得更快。
災難性回溯與如何避開它
回溯式引擎,也就是絕大多數的引擎,在比對失敗時會回頭嘗試其他可能。把一個量詞套在另一個量詞裡面,會讓引擎有指數多種切分輸入的方式,所以像 (a+)+$ 這樣的樣式碰上一長串 a 後面接一個 b 的字串,執行時間可以比宇宙存在的時間還久。
這是真實存在的阻斷服務攻擊手法,被歸類為 ReDoS,也確實癱瘓過線上服務:只要請求解析路徑上有一個這樣的正規表示式,遇到精心構造的輸入,就能讓一顆 CPU 核心無限期滿載。Cloudflare 在 2019 年的全球性中斷,起因正是這種形狀的樣式。
三道防線就能涵蓋大部分情況。避免把量詞層層相套,並優先用反向字元類別取代點號:[^"]* 不可能和它後面的東西重疊,也就沒有東西可以回溯。在引擎支援的地方使用原子群組或佔有型量詞。而當樣式來自使用者時,就把它放進附帶逾時的 worker 裡執行,也就是這個測試工具的做法,或改用 RE2 這類線性時間的引擎。
Unicode,以及為什麼 \w 不夠用
這些簡寫字元類別當初是為 ASCII 定義的,到現在大致仍是如此。在 JavaScript 裡,\d 只比對 0-9,\w 只比對 ASCII 的字母、數字和底線,所以用 \w+ 驗證姓名的樣式會拒絕 José、Zoë,以及所有不是用拉丁字母書寫的名字。
Unicode 屬性跳脫才是正確的解法。加上 u 旗標後,\p{L} 可以比對任何文字系統中的字母,\p{N} 比對任何數字字元,\p{Script=Han} 則比對特定的書寫系統。這些寫法比手動把字元類別擴大精確得多,而且 Unicode 之後新增字元時也依然有效。
加上 u 旗標還有第二個理由:沒有它的話,引擎處理的是 UTF-16 編碼單位而不是編碼點,於是點號會比對到半個表情符號,字元類別也可能把代理對切成兩半。就算加上了,一個看得見的字元仍可能由多個編碼點組成,例如國旗、家庭圖案,或以基底字元加結合符號寫成的重音字母;所以要用正規表示式數字元,還是需要 v 旗標的集合運算語法,或一個看得懂字素的函式庫。
什麼時候不該用正規表示式
對固定字串來說,一般的字串方法更清楚也更快:startsWith、includes、split。為了檢查字串結尾是不是 .json 就搬出一個樣式,是個值得改掉的習慣,因為樸素的寫法日後根本不需要解釋。
只要牽涉到巢狀結構,像是 HTML、JSON、原始碼或成對的括號,就該用剖析器。正規表示式沒辦法計算層數,所以每一個看似能處理這些東西的樣式,其實只是剛好命中常見情況,其餘情況則默默出錯。那則關於用正規表示式剖析 HTML 的著名 Stack Overflow 回答之所以好笑,正是因為它背後的道理完全正確。
面對結構化資料,通常都有專門打造的工具:看得懂引號內逗號的 CSV 剖析器、看得懂 userinfo 與連接埠的 URL 剖析器、看得懂時區的日期函式庫。它們之所以存在,是因為那個用正規表示式寫的版本已經被寫過幾千次,而且每一次都錯得很細微。
你會反覆用到的樣式
修剪與正規化空白:把 \s+ 換成一個空格可以把連續空白壓成一個,而在沒有 trim 函式的語言中,^\s+|\s+$ 可以去掉頭尾的空白。擷取引號內的值:用 "([^"]*)"。要用某個不會出現在欄位內容裡的分隔符號來切分時,請用反向字元類別,而不是惰性的點號。
有兩種樣式值得你不要寫。用正規表示式驗證電子郵件是個陷阱:RFC 5322 的語法真的很複雜,務實的檢查方式是本地部分不為空、有一個 @ 符號、網域中有一個點,然後寄出一封確認信,那才是唯一真正的驗證。URL 驗證也是同一類問題,而現在每個語言都內建了能好好回答這個問題的 URL 剖析器。
當一個樣式長到需要解釋時,請使用具名群組,並在支援的語言中使用允許在樣式內寫空白與註解的延伸旗標。一個你半年後還讀得懂的樣式,值得今天多寫兩行。
常見問題
- 什麼是災難性回溯?
- 某些樣式在「幾乎符合」的輸入上會耗費指數時間。典型形式是量詞巢狀,例如以 (a+)+$ 比對一長串 a 後面接其他字元:引擎會嘗試所有可能的切分方式才放棄,CPU 被占用數秒甚至數分鐘都很常見。
- 為什麼這個工具不會因此凍結?
- 執行中的正規表達式無法從 JavaScript 中止,因此比對改在 Web Worker 中進行。若超過 1.5 秒,該 Worker 會被終止並由新的取代。頁面因此保持回應,也會告訴您發生了什麼事。
- 這些旗標的作用是什麼?
- g 會找出所有符合項目而非只有第一個,i 忽略大小寫,m 讓 ^ 與 $ 也在換行處生效,s 讓 . 可以匹配換行,u 啟用完整的 Unicode 處理(含 \p{...} 屬性跳脫),y 則將比對固定在目前位置。
- 取代時如何引用擷取群組?
- 編號群組使用 $1、$2,具名群組使用 $<name>,整個符合內容使用 $&,若要輸出錢字號本身則用 $$。
- 這和我程式使用的引擎相同嗎?
- 本工具使用瀏覽器的 JavaScript 引擎,因此結果與 JavaScript 完全一致。PCRE、Python、Go 與 .NET 在後行斷言、具名群組語法與 Unicode 處理上有所不同,請在實際執行環境再確認。
- 我的測試資料會被傳送嗎?
- 不會。樣式與文字都留在您的瀏覽器中,因此可安全用於日誌與正式環境的樣本資料。
最後更新:
參考資料
MDN — JavaScript RegExp最近更新
- — 新增可執行的範例、預期結果、限制說明及參考連結。
請提供操作步驟、預期結果及實際結果。請移除機密與個人資料。
