跳到主要內容
楓葉工具箱 ToolMaple

開發最後更新:

Regex 正規表示式測試

寫好 pattern 貼段文字,馬上看到哪幾段符合、抓到哪些分組。

點擊任一範本、自動填入 pattern + 對應範例測試文字。

匹配結果(2 個)

聯絡:hello@toolmaple.com 支援:support@example.com 隨機:abc xyz
詳細:分組與位置(2 個)
  • match: hello@toolmaple.comindex: 3
  • match: support@example.comindex: 26

什麼是正規表達式?為什麼用讀的看不出問題

正規表達式(Regular Expression,簡稱 Regex 或 RegExp)是「用一串符號描述一組字串模式」的語言。 理論基礎 1956 年由數學家 Stephen Kleene 提出,1968 年起進入 UNIX 工具(grep、sed、awk)成為實用方言,1994 年 Perl 5 的擴充語法(PCRE)影響至今所有主流語言。今天 JavaScript、Python、Java、Go、Ruby 幾乎都支援,語法 90% 互通,剩 10% 差異是各家方言。

本頁把你輸入的內容用 new RegExp(pattern, flags) 編譯,再拿瀏覽器裡原本就有的 JavaScript 引擎去比對你的測試文字,所以這裡收的語法是 ECMAScript,跟你在 Node.js 與瀏覽器主控台得到的一樣。它不是 PCRE、不是 Python 的 re,也不是 POSIX 的 grep。

要「跑」一次而不是用讀的,是因為 regex 失敗得很安靜。一個編得過的 pattern 可以高高興興地比對到錯的範圍、多吃掉一個字元,或是你以為有三十個結果它只找到三個,而程式碼上完全看不出來。光讀 a.*b 不會告訴你貪婪的 .* 一路跑到了那一行最後一個 b。把匹配就地標起來、底下再列出位置與捕獲分組,猜測才會變成看得見的東西。

看結果之前有三件事要先知道。本頁完全照你的旗標跑,所以沒開 g 就只顯示第一個匹配,這在測一條兩端綁死的驗證 pattern 時反而是正確行為。重疊的匹配不可能出現,因為引擎會從上一個匹配的結尾繼續往下掃。還有,有參與匹配但什麼都沒抓到的分組會顯示成空字串而不是被省略,這正是你抓出「那個可有可無的分組其實從來沒中過」的方法。

哪個旗標、哪個分頁、哪種情境

支援的 Regex 旗標:

  • g(global):找出所有匹配。預設開啟,可以關。
  • i(ignoreCase):大小寫不敏感
  • m(multiline):`^` 與 `$` 對應每行的開頭結尾
  • s(dotAll):`.` 可以匹配換行
  • u(unicode):完整 Unicode 支援(含 Emoji 等 surrogate pair)
  • y(sticky):lastIndex 開始的位置匹配

實務上最常被忘記的是 m:pattern 在一行裡好好的,貼上整份 log 就什麼都抓不到,多半是 ^ 還在對整段文字的開頭。做表單驗證、兩端用 ^ 與 $ 綁死時則要反過來把 g 關掉,只看第一個結果才是對的。

常見使用情境:

  • 表單驗證:email、手機、身分證、密碼複雜度檢查(前端即時提示)
  • Log / 設定檔解析:從 server log 撈出時間戳、IP、錯誤代碼,用 Regex 切欄位
  • 大量取代:VS Code、Sublime 的 Find & Replace 開 Regex 模式,一次改幾百個檔案
  • 資料清理:CSV 欄位裡的全形空白、零寬字元、HTML entity,一次清掉
  • 網頁爬蟲:HTML / 純文字裡抓 URL、email、電話、價格
  • 程式碼搜尋:找 console.log 殘留、TODO 註解、未使用的 import

兩個分頁回答的是不同問題。「測試(match)」回答 pattern 有沒有中、中在哪裡;「替換(replace)」回答改完之後文字長什麼樣,而那才是你要對整個專案跑搜尋取代之前真正該問的問題。上方的「常用範本(台灣)」直接把身分證、統編、手機條碼載具、發票號碼、+886 手機、車牌、民國日期這些本地格式連同會中的範例文字一起填好,從那裡改比從零開始快。套用之後範本列下面會寫出這條只驗什麼;身分證與統編要另外算檢查碼,發票號碼要對獎,說明旁邊會連到站內能做的那支工具。

實戰教學:從一段真文字長出 pattern

  1. 先把真的文字貼進測試欄,再開始寫。照想像中的輸入寫出來的 pattern,通常在真檔案的第一行就破功。
  2. 從「能動的最鬆版本」開始。要抓錯誤碼就先打 ERROR,看它亮起來,再長成 \bERROR\b.*,這時你已經看得到它順手撈進了什麼。
  3. 把 g 打開,一次看到全部;文字有很多行、pattern 又用到 ^ 或 $ 的話,m 也要打開。
  4. 一次只收緊一個地方。把 . 換成明確的字元組,例如 [^\s] 或 \d;標示範圍跑過頭就把 .* 換成 .*?。
  5. 把真正要的那一段用括號包起來變成捕獲分組,然後展開「詳細:分組與位置」,確認第一個分組剛好是你要的子字串,不是整行。
  6. 最後試著弄壞它。貼一行「不該中」的文字,確認結果數掉到零。沒被餵過壞輸入的驗證 pattern,等於沒測過。

常用 Regex 範例:

  • Email:\b\w+@\w+\.\w+\b
  • URL:https?://[^\s]+
  • 台灣手機:09\d{8}
  • 台灣手機含國際寫法:(?:\+886[- ]?|0)9\d{2}[- ]?\d{3}[- ]?\d{3}(+886 後面要拿掉開頭的 0,+8860912… 是常見的錯誤寫法,這條不收)
  • 身分證字號與新式統一證號:[A-Z][1289]\d{8}(只驗形狀,檢查碼要另外算,可以用身分證產生器與驗證)
  • 電子發票手機條碼載具:\/[0-9A-Z.+-]{7}(斜線開頭共 8 碼,後 7 碼只有數字、大寫英文與 + - . 三個符號;格式對了不代表條碼存在)
  • 統一發票號碼:[A-Z]{2}-?\d{8}(2 碼英文字軌加 8 碼數字,證明聯上印成 AB-12345678)
  • IP v4:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}
  • 日期 YYYY-MM-DD:\d{4}-\d{2}-\d{2}

語法速查(最常用 8 個):

  • .:任意單一字元(換行除外,加 s flag 才含換行)
  • \d \w \s:數字 / 英數底線 / 空白;大寫版 \D \W \S 是反向
  • [abc] [a-z]:字元組(其中一個)。前面加 ^ 變排除
  • * + ?:重複 0+、1+、0 或 1 次
  • {n} {n,} {n,m}:剛好 n 次 / 至少 n 次 / n 到 m 次
  • ^ $:行首 / 行尾(搭配 m flag 更實用)
  • (...):分組 + 捕獲;(?:...) 只分組不捕獲
  • |:或;\b:詞邊界(單字外圍)

實戰教學:用捕獲分組做搜尋與取代

  1. 先在「測試(match)」分頁把匹配弄對。拿一個沒親眼看過標示的 pattern 去取代,就是大量編輯出事的標準劇本。
  2. 切到「替換(replace)」寫取代字串。編號分組是 $1、$2 依此類推,命名分組是 $<name>,整個匹配是 $&,要一個真的錢字號就寫 $$。
  3. 大量編輯時 g 要開著。沒開只會改掉第一個,預覽一長就很容易漏看。
  4. 預覽要從最後面往回讀,不要只看開頭。錯誤常躲在最後一行:某個可有可無的分組沒中,$2 就悄悄變成空字串。
  5. 把 pattern 與取代字串一起複製進編輯器,在那邊跑同一次編輯。重排日期是很好的第一題:抓 (\d{4})-(\d{2})-(\d{2}),取代成 $3/$2/$1。

取代字串一長,命名分組就值得用。抓 (?<year>\d{4})-(?<month>\d{2}),取代成 $<month>/$<year>,半年後再讀還說得出自己在做什麼,$2/$1 則不會。想把民國年跟西元年互換的規則寫進程式前,先在民國年換算把兩種年份的實際字串對一次,再回來改 pattern。

把 pattern 帶進編輯器、終端機或程式碼

欄位裡放的是「裸的 pattern」,前後沒有斜線。它接下來要去哪裡,決定了你得多跳脫幾層:

  • JavaScript 的正規表達式字面值:前後加斜線、旗標放後面,寫成 /\d+/gi,其他什麼都不用改。
  • JavaScript 的字串:每個反斜線都要寫兩次,因為字串字面值會先吃掉一層。這是「在測試器會、放進程式碼就不會」最常見的單一原因。
  • VS Code 與多數編輯器:打開搜尋,點 .* 圖示開啟 regex 模式,pattern 不要帶斜線直接貼。大小寫與全字拼寫是獨立的按鈕而不是旗標,取代欄位的 $1 語法則跟這裡一樣。
  • 終端機:pattern 要用單引號包起來,免得 shell 先去展開它,例如 grep -E 'pattern' file.log。命令列工具用的是另一套方言,下一節會講。
  • 傳給同事:pattern、旗標與一行範例輸入要一起給。單獨一條 pattern 沒辦法 review,是那行範例讓 bug 變得明顯。

同一條 pattern 換到別的語言與工具

技術細節:本工具用瀏覽器原生 RegExp(V8、SpiderMonkey、JavaScriptCore),效能與 Node.js 一致。支援 ECMAScript 2024 規格:命名分組((?<name>...))、後行斷言((?<=...) (?<!...))、Unicode property escapes(\p{L} 匹配任何語言的字母)。Safari 16.4 以下不支援後行斷言,需注意。

regex 大部分是可以搬的,搬不動的通常集中在命名分組的寫法、Unicode 底下那些簡寫字元組到底代表什麼,以及引擎會不會回溯。先看本頁跑的 JavaScript,字面值與建構子只差在跳脫:

const re = /(?<user>[\w.+-]+)@(?<host>[\w.-]+)/gi
const same = new RegExp('(?<user>[\\w.+-]+)@(?<host>[\\w.-]+)', 'gi')
text.match(re)                // 開了 g,回傳所有匹配的陣列
re.exec(text).groups.user     // 下一個匹配的命名分組

Python 的命名分組拼法不一樣,而且對 str 預設就是 Unicode 感知的,所以那邊的 \d 連其他文字系統的數字都算,除非你明講要 ASCII。標準函式庫也沒有 \p{...} 這種屬性跳脫:

import re
m = re.search(r'(?P<user>[\w.+-]+)@(?P<host>[\w.-]+)', text, re.IGNORECASE)
m.group('user')
re.findall(r'\d+', text, re.ASCII)   # 只抓 ASCII 數字

Java 的命名分組跟 JavaScript 一樣寫成 (?<name>...),但它的 \d 與 \w 預設只認 ASCII,要開 UNICODE_CHARACTER_CLASS 才會變寬,而且 Java 字串字面值裡每個反斜線一樣要寫兩次。Go 與 Rust 用的是 RE2 引擎,它為了保證線性時間,乾脆完全不支援反向參照與環顧,帶 (?<=...) 的 pattern 在那邊根本編不過,解法是改用分組把前後文一起抓下來。

命令列的預設比上面任何一個都更舊、更窄。純 grep 與 sed 收的是 POSIX 基本表達式,+ 與 ? 不跳脫就只是普通字元;加上 -E 兩者都會切到延伸表達式,那才是大多數人心裡想的那一套。GNU grep 另外有 -P 走 PCRE 方言,最接近你在這裡測到的行為,但 macOS 內建的是 BSD grep,沒有這個選項,要嘛 brew 裝 GNU 版,要嘛改寫成 -E 的形式:

grep -E '09[0-9]{8}' contacts.txt      # 延伸表達式,到哪都能跑
grep -P '09\d{8}' contacts.txt        # PCRE,只有 GNU grep 有
sed -E 's/([0-9]{4})-([0-9]{2})/\2\/\1/' dates.txt

取代的語法也跟著變:sed 與多數 POSIX 工具用 \1,JavaScript 用 $1。資料庫又是另一套。Postgres 有 ~ 與 regexp_match,走它自己的 POSIX 風味;MySQL 8 的 REGEXP_LIKE 底層是 ICU;SQLite 認得 REGEXP 運算子卻沒有內建實作,應用程式沒有自己註冊一個就會直接報錯。要跑在資料庫裡的 pattern,請當成另一個需要單獨測試的東西,不要當成這裡這條的副本。

注意事項與隱私

測試資料含個資怎麼辦。拿真實 log 或客戶資料來試 pattern 是常態,這裡的比對用的是 瀏覽器自己的 RegExp 引擎,文字留在你的分頁裡, 不需要先把個資碼掉再貼。工具會把最近五組 pattern 與測試文字存在這台瀏覽器的 localStorage,讓歷史紀錄能還原,按歷史紀錄裡的清除就會刪掉;整個工具區也標了錄影遮罩,回顯出來的內容不會被收走。

巢狀量詞會把你的分頁卡死。這裡的引擎會回溯,所以像 (a+)+b 這種 pattern 遇到很長又不匹配的字串,可能嘗試指數級數量的組合。那也就是這種 pattern 跑到伺服器上會變成 ReDoS 阻斷服務攻擊的原因。比對是在本頁的主執行緒上跑的,所以分頁會直接凍住而不是跳錯誤訊息。匹配結果列表最多 10,000 筆當作安全閥,意思是太寬鬆的 pattern 配上很長的文字,只會顯示前一萬個。

regex 不是 parser。HTML、JSON、含引號逗號的 CSV 與程式碼,全都是有巢狀或需要看上下文的結構,沒有任何正規表達式能正確處理巢狀。用 pattern 找候選、或做一次你事後會親自檢查的編輯可以,正確性要緊時請用真正的解析器。email 同理:RFC 5322 比任何實用 pattern 都寬鬆得多,所以驗證要寬鬆,最後靠寄一封信確認。

瀏覽器差異很小但真的存在。後行斷言((?<=...) 與 (?<!...))在 Chrome 與 Firefox 已經好幾年,Safari 是 2023 年的 16.4 才跟上,所以在你筆電上編得過的 pattern,在比較舊的 iPhone 可能回報語法錯誤。看到解釋不了的錯誤,先測這一項。

常見問題

greedy 跟 lazy 差在哪?

預設量詞是 greedy(貪婪),會盡量吃多。例如 a.*b 對 axxxbxxxb 會吃到最後一個 b。在量詞後加 ? 變 lazy(懶惰),會盡量吃少:a.*?b 只吃到第一個 b。寫 HTML / 字串解析時常需要 lazy。

為什麼我的 email regex 抓不到某些 email?

因為 email 規範(RFC 5322)超複雜,所謂「完美的 email regex」長到誇張。實務上用簡化版(如 \S+@\S+\.\S+)擋掉 99% 明顯錯誤就夠,剩下的 0.1% 邊角案例(含中文網域、加號別名)讓伺服器寄驗證信去確認最實在。

什麼是「災難性回溯」(ReDoS)?

當 regex 含「巢狀量詞」(如 (a+)+b)遇到不匹配的長字串時,引擎會嘗試指數級數量的組合,變成卡死。寫 regex 時避免同位置兩個 + 或 * 巢狀,可降低風險。Go / Rust 用的 RE2 引擎不支援回溯,所以絕對不會卡。

PCRE / Python / Java 規則一樣嗎?

90% 一樣,10% 不同。本工具基於 JavaScript 引擎,所以結果跟你在 Node.js / 瀏覽器跑相同。要驗證 Python re 或 Java Pattern 的差異,請用對應語言實測(命名分組語法、unicode 預設行為都有微差)。

為什麼 pattern 在單行有效,貼上整份 log 就抓不到?

幾乎都是少了 m 旗標。沒有 m 的時候,^ 只對整段文字的最開頭、$ 只對最結尾,所以一份多行的 log 裡只有第一行的開頭算數。把 m 打開,^ 與 $ 就會對每個換行生效。如果要讓 . 也吃得到換行(例如抓跨行的註解區塊),那是另一個旗標 s。

沒有開 g 旗標,為什麼只出現一個結果?

那是正確行為。沒有 g 時引擎找到第一個就停,本頁也照你的旗標顯示,所以只會標出一個。做表單驗證、pattern 兩端用 ^ 和 $ 綁死時本來就該關掉 g;要從 log 裡撈出全部的網址或錯誤碼才需要打開。另外本頁的匹配結果最多列 10,000 筆,太寬鬆的 pattern 配上很長的文字只會顯示前一萬個。

在這裡會、放進程式碼就壞掉,通常是什麼原因?

八成是反斜線被吃掉一層。本頁欄位裡放的是「裸的 pattern」,寫成 JavaScript 字面值只要前後加斜線、旗標放後面,例如 /\d+/gi,不用改任何東西;但若是用 new RegExp('...') 這種字串寫法,每個反斜線都要寫兩次,因為字串字面值會先吃掉一個。sed 之類的工具則是連取代語法都不同:JavaScript 用 $1,sed 用 \1。

我貼進去的 log 或個資會被上傳嗎?

不會。pattern 與測試文字都是由你自己的瀏覽器編譯與比對,不會送到伺服器、不會被記錄。工具會把最近五組 pattern 與文字存在這台瀏覽器的 localStorage 供歷史紀錄還原,按歷史紀錄的清除就會刪掉。結果區也標了遮罩,錄影分析不會收走回顯的內容。

資料來源

  • MDN,正規表達式指南與RegExp 參考(旗標、命名分組、後行斷言、Unicode property escapes、lastIndex,以及各瀏覽器的支援表)。
  • 手機條碼載具的字元規則:綠界科技 ECPay 的手機條碼驗證 API 文件(「第 1 碼為 / ,其餘 7 碼則由數字 0-9、大寫英文 A-Z 與特殊符號 +、-、. 這 39 個字元組成」)。財政部電子發票整合服務平台的說明頁擋自動讀取,沒能直接引用。
  • Python Software Foundation,re 模組文件((?P<name>...) 的拼法與 re.ASCII 旗標)。
  • Google,RE2 語法(Go 與 Rust 背後的引擎,以及它刻意不做的那些功能)。
  • OWASP,Regular expression Denial of Service(巢狀量詞怎麼變成一場事故)。
  • IETF,RFC 5322的 email 位址語法。命令列的行為出自 POSIX 對 grep 與 sed 的規格,以及 GNU 與 BSD 的說明頁;資料庫的支援各家文件自有說明,且會隨大版本改變。

更多工具

要比對的文字是設定檔或 API 回應時,先用「JSON 格式化」把結構攤開來看,常常會發現你要的其實是查一個 key 而不是寫 regex。 pattern 要塞進查詢字串時,「URL 編碼轉換」會告訴你伺服器實際收到什麼。log 開頭通常是一個值得先換算的Unix 時間戳。

相關工具

隱私:pattern 與你的測試文字完全在瀏覽器裡編譯與比對,不會送到伺服器、不會存在伺服器上,也不會被記錄。最近五組 pattern 與文字會留在這台瀏覽器的 localStorage 供歷史紀錄還原,可以從那裡清除。本工具沒有自己的分析事件,結果區也排除在錄影分析之外,pattern 與文字都不會被收集。