正規表示式——全面指南
目錄
1. 簡介
文字——無論是日誌檔案、配置資料、使用者輸入還是文件——在程式設計中無處不在。從簡單的指令碼到完整的應用,開發者經常需要搜尋、驗證、提取或轉換文字片段。雖然許多 IDE 和編輯器(如 Notepad++、VS Code 或 Sublime Text)提供了基本的尋找和替換功能,但當你需要處理模式、條件邏輯或重複結構時,這些功能很快就會捉襟見肘。這正是正規表示式(regex)的用武之地。
從本質上講,正規表示式是一種描述一組字串的緊湊、宣告式的方式。比如說,你不必手動逐行掃描文字來找出日誌中的所有警告訊息,而是可以編寫一個模式,匹配時間戳、日誌級別、檔名、電子郵件地址、URL——任何你能想到的內容。一旦理解了基礎知識,正規表示式就可能成為你開發者工具箱中最強大的“武器”之一。
來看一段典型的日誌:
2025-05-31 14:23:48 [Info] Starting application...
2025-05-31 14:23:50 [Info] Loading application data...
2025-05-31 14:23:55 [Warning] Missing font file: New Times Roman.
2025-05-31 14:24:22 [Info] The application has started, with 1 warning.
2025-05-31 14:25:01 [Error] Unhandled exception: NullReferenceException.
如果你想回答這樣的問題:
- “發出了哪些警告,分別在什麼時候?”
- “哪些行包含錯誤訊息?”
- “應用啟動了多少次?”
你可以用自己喜歡的語言編寫一個指令碼,按空格拆分、檢查各個標記並進行字串比較。但這很快就會變得繁瑣:時間戳格式的多變、可選欄位的處理、空白字元的各種怪異情況,都可能讓你的程式碼變得脆弱。
然而,藉助一個精心編寫的正規表示式,你可以匹配整行日誌,並捕獲時間戳、日誌級別以及訊息的其餘部分——全部在一個模式中完成。更重要的是,正規表示式可以處理任何具有一致結構的基於文字的格式:CSV 檔案、HTML/XML 標籤、自訂配置格式,等等。
讀完本文後,你將:
- 理解正規表示式背後的基本動機。
- 瞭解適用於日常任務的實用語法元素和示例。
- 擁有一份彙總最常用正規表示式結構的便捷參考。
無論你只是在編輯器中做過一些“尋找和替換”,還是編寫過簡單的字串處理程式碼,本指南都將幫你跨越這道鴻溝,並向你展示如何藉助正規表示式更上一層樓。
2. 線上測試器
替換(可選)
3. 實用語法
本節將介紹正規表示式模式的核心構建塊。每個小節先介紹一個概念,然後給出真實場景中的示例——有些基於日誌處理,有些則涉及驗證電子郵件地址或從文字中提取數字等日常任務。
3.1 字面字元與簡單匹配
在最簡單的層面上,正規表示式可以只由字面字元組成。例如:
- 模式
Error會匹配文字中出現的任何子字串“Error”。 - 模式
2025-05-31會匹配這個確切的日期字串。
然而,真實世界中的文字很少能完美對齊。例如,日誌訊息中包含不斷變化的時間戳和不同的日誌級別。要以通用的方式匹配它們,你需要用到元字元。
要點: 正規表示式中的大多數字符都按字面匹配其自身,特殊的元字元(如
.、*、?、\等)除外。要匹配字面意義上的句點、方括號或其他元字元,你必須用反斜槓對其進行“轉義”(用\.匹配字面的點,用\[匹配字面的左方括號,依此類推)。
3.2 字元類([])與範圍
字元類是用方括號括起來的一組字元,它恰好匹配該組中的一個字元。例如:
[AEIOU]匹配任意一個大寫母音字母。[0-9]匹配 0 到 9 之間的任意一個數字。[A-Za-z0-9_]匹配一個典型的單詞字元(字母、數字或底線)。
示例 1:匹配四位數年份
假設你想匹配 1900 到 2099 之間的任意年份:
^(19|20)[0-9]{2}$
^和$是錨點(我們稍後會介紹)。(19|20)匹配“19”或“20”。[0-9]{2}恰好匹配兩個數字。- 合在一起,它匹配“1900”到“2099”。
示例 2:提取日誌級別
給定這樣一行日誌:
2025-05-31 14:23:55 [Warning] Missing font file: New Times Roman.
你可以用以下模式匹配日誌級別(Info、Warning、Error 等):
\[(Info|Warning|Error|Debug)\]
\[和\]匹配字面的方括號。(Info|Warning|Error|Debug)匹配這四個單詞之一。
如果你預計還會有其他級別(例如 Trace 或 Fatal),可以把它們加到括號內:
\[(?:Info|Warning|Error|Debug|Trace|Fatal)\]
這裡我們使用了 (?: … ) 來建立一個非捕獲組(我們將在第 5 節中詳細討論分組)。
3.3 量詞:重複控制
量詞指定前面的元素應該匹配多少次。最常見的量詞有:
*——匹配零次或多次(貪婪)。+——匹配一次或多次(貪婪)。?——匹配零次或一次(使前面的標記成為可選)。{n}——恰好匹配 n 次。{n,m}——匹配 n 到 m 次(包含兩端)。{n,}——匹配 n 次或更多次。
示例 3:匹配時間戳
像 2025-05-31 14:23:48 這樣的時間戳可以用以下模式匹配:
\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}
\d是[0-9]的簡寫。\d{4}恰好匹配四個數字(年份)。-匹配字面的連字元。\d{2}匹配兩個數字(月和日)。\s+匹配一個或多個空白字元(空格、製表符)。\d{2}:\d{2}:\d{2}匹配HH:MM:SS。
如果你確定日期和時間之間恰好只有一個空格,可以用 \s 代替 \s+。
示例 4:可選的副檔名
假設你有 report.txt 或 report 這樣的檔名。要同時匹配兩者,可以使用:
^report(?:\.txt)?$
^和$確保我們匹配整個字串。(?:\.txt)?的意思是“可選地匹配‘.txt’”。\.匹配字面的點。txt匹配“txt”。- 組後面的
?使其成為可選。
3.4 錨點:定位匹配
錨點指定的是文字中的位置,而不是實際的字元:
^——行/字串的開頭。$——行/字串的結尾。\b——單詞邊界(位於\w和\W之間)。\B——非單詞邊界。
示例 5:以“Error”開頭的行
如果你想找出所有以單詞“Error”(作為獨立單詞)開頭的行,可以這樣寫:
^Error\b.*$
^Error確保該行以“Error”開頭。\b斷言此處為單詞邊界,這樣“Errors”或“ErrorCode”就不會被匹配。.*匹配該行的其餘部分(任意字元,零次或多次)。$錨定行尾。
3.5 分組與捕獲
圓括號 ( 和 ) 有兩個主要用途:
- 分組:將多個標記視為一個整體(通常與量詞或選擇結構一起使用)。
- 捕獲:儲存該組匹配到的文字,以便之後取用(在程式碼中或替換字串中)。
示例 6:捕獲時間戳和級別
給定:
2025-05-31 14:23:55 [Warning] Missing font...
你可以這樣寫:
^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2})\s+\[(Info|Warning|Error|Debug)\]\s+(.*)$
(\d{4}-\d{2}-\d{2})捕獲日期(Group 1)。(\d{2}:\d{2}:\d{2})捕獲時間(Group 2)。(Info|Warning|Error|Debug)捕獲日誌級別(Group 3)。(.*)捕獲訊息的其餘部分(Group 4)。在大多數語言中,匹配之後你可以提取 Group 1、Group 2 等:
- 例如在 C# 中:
match.Groups[1].Value給出日期。
- 例如在 C# 中:
如果你想分組但不捕獲(用於選擇結構或量詞),請使用 (?: … )。例如:
(?:Info|Warning|Error|Debug)
不會建立帶編號的反向引用。
3.6 常用元字元與轉義序列
一些最常用的簡寫和元字元包括:
.——匹配除換行符號以外的任意字元(在大多數風格中如此;有些風格允許啟用“dotall”模式來匹配換行符號)。\d——數字(等價於[0-9])。\D——非數字(等價於[^0-9])。\w——單詞字元(字母、數字、底線;[A-Za-z0-9_])。\W——非單詞字元(任何不屬於\w的字元)。\s——空白字元(空格、製表符、換行符號等)。\S——非空白字元。\t、\n、\r——製表符、換行符號、回車符。
提示: 在程式碼中編寫正規表示式時,你通常需要對反斜槓進行轉義。例如,在 C# 中,你可以使用逐字字串字面量(
@"\d{4}-\d{2}-\d{2}")來避免雙重轉義。
3.7 實用示例:提取電子郵件地址
假設你有一份文件或配置檔案,需要從中提取所有電子郵件地址。一個簡單(但並不完全符合 RFC)的模式是:
\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b
\b——確保從單詞邊界開始。[A-Za-z0-9._%+-]+——一個或多個字母、數字、點、底線、百分號、加號或連字元(@之前的本地部分)。@——字面的“@”符號。[A-Za-z0-9.-]+——一個或多個字母、數字、點或連字元(域名)。\.——字面的點。[A-Za-z]{2,}——頂級域名至少包含兩個字母(例如“com”、“org”、“io”)。\b——在單詞邊界處結束,以避免部分匹配(這樣就不會匹配到末尾的標點符號)。
測試一下
給定:
Please contact alice@example.com or bob.smith@my-domain.io for more info. Alternatively, reach out to admin@localhost if testing locally.
alice@example.com匹配。bob.smith@my-domain.io匹配。admin@localhost不匹配,因為localhost沒有“.+ 兩個或更多字母”形式的頂級域名。
3.8 實用示例:驗證北美電話號碼
北美編號計劃(NANP)的電話號碼通常是這樣的:
123-456-7890(123) 456-7890123.456.7890+1 (123) 456-7890
一個實用的正規表示式可以是:
^(\+1\s?)? # Optional country code +1 and a space
(?:\(\d{3}\)|\d{3}) # Either (123) or 123
[ .-]? # Separator: space, dot, or hyphen (optional)
\d{3} # Next three digits
[ .-]? # Separator again (optional)
\d{4}$ # Last four digits
壓縮之後(去掉空白和註解):
^(\+1\s?)?(?:\(\d{3}\)|\d{3})[ .-]?\d{3}[ .-]?\d{4}$
^和$將模式錨定到整個字串。(\+1\s?)?——可選地匹配“+1”,其後可以跟一個可選的空格。(?:\(\d{3}\)|\d{3})——括號內的三個數字,或者不帶括號的三個數字。[ .-]?——可選地匹配一個空格、點或連字元。\d{3}——三個數字。[ .-]?——再次匹配分隔符。\d{4}——四個數字。
這個模式可以匹配大多數常見的北美號碼格式。通過調整分組和量詞,你可以使其適用於其他地區,或採用更嚴格/更寬鬆的規則。
3.9 實用示例:解析日誌檔案
回到我們的日誌示例,假設你只想提取警告訊息及其時間戳。你可以使用這樣的正規表示式:
^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[Warning\]\s+(.*)$
- Group 1 捕獲時間戳(
2025-05-31 14:23:55)。 - Group 2 捕獲訊息本身(
Missing font file: New Times Roman.)。
在大多數程式語言中:
C#(使用
System.Text.RegularExpressions):var pattern = @"^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[Warning\]\s+(.*)$"; var matches = Regex.Matches(logContents, pattern, RegexOptions.Multiline); foreach (Match m in matches) { string timestamp = m.Groups[1].Value; string message = m.Groups[2].Value; Console.WriteLine($"[{timestamp}] Warning: {message}"); }Python(使用
re模組):import re pattern = r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[Warning\]\s+(.*)$' for line in log_contents.splitlines(): m = re.match(pattern, line) if m: timestamp = m.group(1) message = m.group(2) print(f"[{timestamp}] Warning: {message}")
即使你只在編輯器中按一下過“Replace”(替換),這些示例也應該能讓你清楚地感受到一個正規表示式有多麼強大。你定義了“匹配直到 [Warning] 為止的所有內容,然後捕獲其後的任何內容”,就能準確得到你想要的資料。
3.10 測試與除錯正規表示式
剛接觸正規表示式時,很容易忽略一些小錯誤——漏掉轉義、放錯量詞的位置、忘記錨點。下面是一些建議:
使用線上測試器 regex101.com(PCRE、JavaScript、Python)、RegexStorm(適用於 .NET)或 regexr.com 等網站可以讓你輸入模式、貼上測試文字,並即時檢視匹配結果和高亮顯示。
從簡單開始,逐步擴充 如果你需要一個複雜的模式,先從一小部分開始。例如,先只用
\d{4}-\d{2}-\d{2}測試日期,然後加上時間,再加上日誌級別,依此類推。為模式新增註解(在支援的情況下) 一些語言/風格支援“擴充”或“自由間距”模式,在這種模式下,你可以在正規表示式中新增空白和
# comments:(?x) # Enable free-spacing mode ^ # Start of line (\d{4}-\d{2}-\d{2}) # Group 1: Date \s+ # One or more spaces (\d{2}:\d{2}:\d{2}) # Group 2: Time \s+ # One or more spaces \[Warning\] # Literal [Warning] \s+ # One or more spaces (.*) # Group 3: Message text $這讓維護和日後的修改更加容易,對於複雜的正規表示式尤其如此。
儘早轉義 如果你需要匹配特殊字元(
.、*、?、+、[、]、(、)、|、^、$、\),除非你明確需要它們的特殊行為,否則一律用反斜槓對其進行轉義。
4. 全面參考
下面彙總了最常用的正規表示式結構。可以把它當作一份快速參考的“速查表”。
注意: 不同的正規表示式風格(PCRE、Python、JavaScript、.NET、Java 等)在一些細節上有所不同,但下面的大部分條目都是通用的。存在特定於風格的說明時,會加以註明。
4.1 基本元字元
| 元字元 | 含義 |
|---|---|
. |
匹配除換行符號以外的任意字元(除非啟用了“dotall”/“singleline”模式)。 |
^ |
匹配行/字串的開頭。 |
$ |
匹配行/字串的結尾。 |
\b |
單詞邊界(\w 與 \W 之間的過渡處)。 |
\B |
非單詞邊界(與 \b 相反)。 |
\d |
數字,等價於 [0-9]。 |
\D |
非數字,等價於 [^0-9]。 |
\w |
單詞字元,等價於 [A-Za-z0-9_]。 |
\W |
非單詞字元,等價於 [^A-Za-z0-9_]。 |
\s |
空白字元(空格、製表符、換行符號、垂直製表符、換頁符等)。 |
\S |
非空白字元(任何不被 \s 匹配的字元)。 |
\t |
製表符。 |
\n |
換行符號(LF)。 |
\r |
回車符(CR)。 |
\f |
換頁符。 |
\v(特定於風格) |
垂直製表符(部分風格支援)。 |
\\ |
字面的反斜槓。 |
\. |
字面的點。 |
\* |
字面的星號。 |
\+ |
字面的加號。 |
\? |
字面的問號。 |
\{ \} |
字面的花括號。 |
\[ \] |
字面的方括號。 |
\( \) |
字面的圓括號。 |
\| |
字面的豎線/選擇運算子。 |
\^ |
字面的脫字元(字串開頭錨點)。 |
\$ |
字面的美元符號(字串結尾錨點)。 |
4.2 量詞
| 量詞 | 含義 |
|---|---|
* |
匹配前面的標記零次或多次(貪婪)。 |
+ |
匹配前面的標記一次或多次(貪婪)。 |
? |
匹配前面的標記零次或一次(使其成為可選;也用於惰性量詞)。 |
{n} |
匹配前面的標記恰好 n 次。 |
{n,} |
匹配前面的標記n 次或更多次(無上限)。 |
{n,m} |
匹配前面的標記至少 n 次但不超過 m 次。 |
*?、+?、??、{n,m}? |
上述量詞的“惰性”或“非貪婪”版本(匹配儘可能少的字元)。 |
貪婪與惰性:
- 貪婪量詞(
*、+、{n,m})會在仍能讓模式其餘部分匹配成功的前提下,嘗試匹配儘可能多的文字。- 惰性量詞(
*?、+?、{n,m}?)會匹配儘可能少的文字,只要整體匹配能夠成功即可。
示例:貪婪與惰性
- 模式
a.*b作用於文字axxxbxxb時,會匹配axxxbxxb(貪婪:從第一個a到最後一個b)。 - 模式
a.*?b會匹配axxxb(惰性:從a到第一個b)。
4.3 字元類與字符集
| 語法 | 匹配 |
|---|---|
[abc] |
字元 a、b 或 c 中的任意一個。 |
[^abc] |
除 a、b 或 c 之外的任意字元。 |
[a-z] |
從 a 到 z 的任意小寫字母。 |
[A-Z] |
從 A 到 Z 的任意大寫字母。 |
[0-9] |
從 0 到 9 的任意數字。 |
[A-Za-z0-9_] |
任意“單詞”字元(通常等價於 \w)。 |
[[:alnum:]](POSIX) |
字母和數字(POSIX 風格)。可在部分風格中使用(例如 GNU、支援 [[:alnum:]] 的 PCRE)。 |
[[:space:]]、[[:digit:]] 等 |
POSIX 字元類(各風格的支援情況不一)。 |
示例:匹配十六進位制數字
^[0-9A-Fa-f]+$
- 匹配任何完全由十六進位制數字組成的非空字串。
4.4 選擇(|)與分組
分組:
( … )- 捕獲組:儲存匹配到的文字,以供反向引用或提取。
- 非捕獲組:
(?: … )不儲存匹配內容;適用於只需分組而無需捕獲的場合。
選擇:
A|B匹配模式 A 或模式 B。
示例:匹配多種副檔名
^.*\.(?:jpg|jpeg|png|gif|bmp)$
.*\.匹配直到點(含點)為止的任意檔名部分。(?:jpg|jpeg|png|gif|bmp)匹配這五種副檔名之一。$確保副檔名位於末尾。
4.5 錨點與邊界
| 錨點 | 含義 |
|---|---|
^ |
字串開頭(在多行模式下為行首)。 |
$ |
字串結尾(在多行模式下為行尾)。 |
\A |
字串開頭(不受多行模式影響)。 |
\z 或 \Z |
字串結尾(\z 是絕對結尾;\Z 允許末尾有一個可選的換行符號)。 |
\b |
單詞邊界(位於 \w 和 \W 之間)。 |
\B |
非單詞邊界(與 \b 相反)。 |
多行模式(
m標誌)
- 如果啟用多行模式(通常通過一個標誌,例如 .NET 中的
RegexOptions.Multiline或 Python 中的re.MULTILINE),^和$將匹配每一行的開頭/結尾,而不是整個字串的開頭/結尾。
4.6 環視(零寬斷言)
環視讓你可以斷言某個匹配之前或之後是(或不是)某些內容,而不會把這些“內容”包含在匹配結果中。
| 結構 | 含義 |
|---|---|
(?= … ) |
正向先行斷言:其後的內容必須匹配 …。 |
(?! … ) |
負向先行斷言:其後的內容必須不匹配 …。 |
(?<= … ) |
正向後行斷言:其前的內容必須匹配 …。 |
(?<! … ) |
負向後行斷言:其前的內容必須不匹配 …。 |
示例:匹配不含數字的密碼
要匹配一個長度為 6–12 個字元且不包含任何數字的密碼:
^(?=.{6,12}$)(?!.*\d).+$
(?=.{6,12}$)——確保長度在 6 到 12 之間。(?!.*\d)——確保任何位置都沒有數字(.*\d的意思是“某處有一個數字”;(?! … )表示“字串的其餘部分不得匹配.*\d”)。.+$——最後,匹配一個或多個(任意)字元,直到結尾。
4.7 反向引用
捕獲一個組之後,你可以在模式的後面部分引用它(以強制要求文字的兩個部分完全相同),也可以在替換字串中引用它。
\1、\2……引用 Group 1、Group 2 等匹配到的文字(在大多數風格中如此)。- 在替換字串中(例如在 .NET 或 JavaScript 中),你可能會使用
$1、$2等。
示例:匹配 HTML/XML 的開始標籤和結束標籤
要確保開始標籤 <tag> 由同名的 </tag> 閉合:
^<([A-Za-z][A-Za-z0-9]*)\b[^>]*>(.*?)</\1>$
<([A-Za-z][A-Za-z0-9]*)\b[^>]*>- 在 Group 1 中捕獲標籤名(例如
div、span、p)。 \b確保標籤名之後是一個單詞邊界。[^>]*匹配開始標籤中的任何屬性。
- 在 Group 1 中捕獲標籤名(例如
(.*?)- 惰性地捕獲元素內的任何內容(Group 2)。
</\1>- 匹配與 Group 1 中捕獲的標籤名完全相同的結束標籤(例如
</div>)。
- 匹配與 Group 1 中捕獲的標籤名完全相同的結束標籤(例如
這個簡單的模式可以匹配像 <p class="intro">Hello</p> 這樣的完整元素,但它無法處理同名標籤的巢狀元素。對於巢狀結構,你需要更高階的解析方法(通常超出了基本正規表示式所能穩健處理的範圍)。
4.8 常用簡寫與轉義序列
| 序列 | 含義 |
|---|---|
\d |
數字(與 [0-9] 相同)。 |
\D |
非數字(與 [^0-9] 相同)。 |
\w |
單詞字元 [A-Za-z0-9_]。 |
\W |
非單詞字元 [^A-Za-z0-9_]。 |
\s |
空白字元 [ \t\r\n\f\v]。 |
\S |
非空白字元。 |
\t、\n、\r、\f |
製表符、換行符號、回車符、換頁符。 |
\0 |
空字元(U+0000)。 |
\xhh |
十六進位制程式碼為 hh(兩位十六進位制數字)的字元。 |
\uhhhh |
程式碼為 hhhh(四位十六進位制數字)的 Unicode 字元。(特定於風格:.NET、Java、帶 u 標誌的 JavaScript 等。) |
\cX |
控制字元(例如 \cA 為 U+0001)。 |
\Q … \E |
在某些風格(如 .NET)中,\Q 開始一個引用區段(直到 \E 為止的所有內容都按字面處理)。 |
4.9 佔有量詞(量詞後加 +)與原子組(特定於風格)
一些正規表示式風格(如 Java、PCRE)支援佔有量詞(*+、++、?+、{n,m}+),它們從不回溯:
示例:
a*+ba*+匹配儘可能多的a,並且不允許通過回溯讓b得以匹配。如果輸入是“aaaaabc”:
a*+匹配“aaaaa”。- 然後引擎嘗試匹配
b。由於下一個字元是b,整體匹配結果為“aaaaab”。
原子組 (?> … ) 有類似的效果:一旦 (?> … ) 內的子模式匹配成功,引擎就不會再回溯到其中。
注意: 並非所有風格都支援佔有量詞或原子組。如果你需要這些高階功能,請查閱你所用語言/執行時的文件。
4.10 標誌(修飾符)與模式
大多數正規表示式引擎允許你通過標誌啟用或禁用某些行為。常見的標誌包括:
| 標誌 | 含義 | 典型語法 |
|---|---|---|
i |
不區分大小寫的匹配(.NET 中為 RegexOptions.IgnoreCase)。 |
內聯:(?i)pattern。 |
m |
多行模式:^ 和 $ 匹配各行的開頭/結尾,而不僅是字串的開頭/結尾。 |
在 .NET 中:RegexOptions.Multiline 或 (?m)pattern。 |
s |
單行模式或 dot-all 模式:. 也匹配換行符號。 |
在 .NET 中:RegexOptions.Singleline 或 (?s)pattern。 |
x |
自由間距/註解模式:忽略空白,並允許使用 # comments。 |
在 .NET 中:RegexOptions.IgnorePatternWhitespace 或 (?x)pattern。 |
U |
非貪婪模式(PCRE):互換貪婪與惰性行為(很少使用)。 | 在 PCRE 中:(?U)pattern。 |
u |
Unicode 模式(特定於風格)。 | 在 JavaScript 中使用 /u 標誌,或在 .NET 中使用 RegexOptions.CultureInvariant。 |
示例:內聯標誌
(?im)^[a-z]+\d+\s+$
(?i)表示“不區分大小寫”。(?m)表示“多行”。- 如果沒有標誌,
^[a-z]+只會匹配小寫字母,並且只在整個字串的開頭匹配;有了i和m,它會在每一行上匹配小寫或大寫字母。
4.11 替換模式
當你使用正規表示式執行替換時,通常會在替換字串中使用反向引用:
- 在許多語言中(例如 JavaScript、Python),使用
$1、$2…… - 在 .NET 中,通常同樣使用
$1、$2。 - 在一些較舊的工具或風格中,可能會使用
\1、\2。
示例:把“Last, First”調換為“First Last”
原始文字:
Smith, John
Doe, Jane
正規表示式尋找模式:
^([A-Za-z]+),\s+([A-Za-z]+)$
替換模式:
$2 $1
替換之後:
John Smith
Jane Doe
([A-Za-z]+)在 Group 1 中捕獲姓氏。([A-Za-z]+)在 Group 2 中捕獲名字。$2 $1在輸出中交換兩者的位置。
5. 結語
到現在,你應該已經紮實地掌握了:
- 正規表示式為什麼對文字處理極為寶貴。
- 如何編寫基本模式:字面字元、字元類、量詞、錨點。
- 如何使用分組、環視和反向引用進行更高階的匹配。
- 哪些標記和結構構成了大多數實用正規表示式的“核心”。
- 需要溫習時去哪裡尋找(上面的參考部分,或 regexstorm.net/reference 這樣的網站)。
只需幾十分鐘的練習——針對示例文字編寫正規表示式——你就能迅速建立信心。請記住:
- 儘早測試、經常測試:在線上測試器或 IDE 的“Find in Files”(在檔案中尋找)中測試——即時看到匹配結果,遠比手動肉眼檢查程式碼更有啟發。
- 可讀性很重要。 如果模式變得難以閱讀,請使用自由間距模式(
(?x))並新增註解。 - 對於常見任務,保持簡單。你很少需要最晦澀的語法;匹配日誌級別、日期、電子郵件地址或基本的 CSV 欄位,通常用簡單直接的字元類和量詞就能完成。
掌握基礎之後,你可以探索更高階的主題,例如:
- 原子分組和佔有量詞(用於對效能要求很高的模式)。
- 遞迴模式(在 PCRE 或一些高階風格中),用於匹配巢狀結構。
- 條件子模式(在 PCRE 或 .NET 中),在決定模式的下一部分之前,先測試某個組是否已經匹配。
不過,這些主題就留待以後再談。現在,請藉助上面的示例和參考表,在你的下一個文字處理挑戰中嘗試運用正規表示式。用不了多久,你就會發現自己開始用模式來思考——而且再也回不去了。
恭喜你踏上了打造模式的旅程!把這份指南放在手邊,收藏你最喜歡的正規表示式測試器,並記住:一個寫得好的正規表示式,往往能把數小時的手動解析變成一行優雅的程式碼。