正規表示式簡介
正規表示式(regular expression,常簡寫作RegExp或regex,尚有「規則運算式」、「正則表達式」等多種譯法)是一種定義文字比對規則的語言,可用於尋找、取代文字。正規表示式應用極為廣泛,大多數程式語言都支援用正規表示式做文字操作,許多應用程式也可以使用,且幾乎不可能被淘汰,是非常值得投資學習的工具。
正規表示式的參考資源極多,以下提供一些通用基本觀念,以便沒有寫程式經驗的朋友瞭解一些簡單但威力無窮的應用方式,及實際用於資料檢索。文末也提供一些參考資源,供有興趣的朋友深入鑽研。
語法簡介
正規表示式中有一些特定符號或符號序列具有特殊意義,一些常用符號簡介如下:
- 「
.
」:表示一個任意字元,例如「a.c
」可匹配「aac」、「abc」。- 預設情況下「
.
」代表的任意字元不包括換行字元,但也可設定為包括。
- 預設情況下「
- 「
{m,n}
」:表示前一字元重複m至n次。例如「go{2,5}gle
」可匹配「google」、「gooogle」、「goooogle」、「gooooogle」。- 逗號和n都可以省略,但m不能省,例如「
go{3}gle
」表示「o」重複3次,「go{5,}gle
」表示「o」重複5次以上,但「go{0,3}gle
」不能寫成「go{,3}gle
」。
- 逗號和n都可以省略,但m不能省,例如「
- 「
*
」:表示前一字元重複0次以上,相當於「{0,}
」。例如「噗哈*
」可匹配「噗」、「噗哈」、「噗哈哈」、……。 - 「
+
」:表示前一字元重複1次以上,相當於「{1,}
」。例如「哈+囉
」可匹配「哈囉」、「哈哈囉」、「哈哈哈囉」、……。 - 「
?
」:表示前一字元重複0至1次,相當於「{0,1}
」。例如「colou?r
」可匹配「color」或「colour」。- 若「
?
」接在重複次數後面,則其意義改為表示「優先選擇最短匹配」。正規表示式引擎的預設做法是「優先選擇最長匹配」,例如對「accccc」用「ac*
」或「ac+
」匹配會得到「accccc」,用「ac{2,4}?
」匹配會得到「acccc」,用「ac?
」匹配會得到「ac」。但用「ac*?
」或「ac??
」匹配會得到「a」,用「ac+?
」匹配會得到「ac」,用「ac{2,4}?
」匹配會得到「acc」。 - 注意:若用「
a*?c
」匹配「aaaaac」會得到「aaaaac」而不是「c」。這是因為正規表示式引擎的運作方式是「由前而後,匹配優先」,也就是由第一個字元前的位置開始,在該位置的文字若能和正規表示式匹配就取作結果並結束,不能匹配則前進至下一字元的位置嘗試。此例由最前面的a之前開始,在這個位置可匹配到「aaaaac」,因此立刻結束並得到此結果。同理,用「a{1,4}?c
」匹配「aaaaac」會得到「aaaac」而不是「ac」,因為首次在第一個a之前無法匹配,於是前進一字,在第二個a之前的位置可匹配到「aaaac」,因此立刻結束並得到此結果。
- 若「
- 「
(x)
」:將x視為子群組,同時記住匹配到的值供之後使用。例如「(0+)7\1
」可匹配「070」、「00700」、「0007000」、……等等(其中「\1
」引用第1個子群組的值)。- 注意:子群組的值是x匹配到的值,而不是x表示式。「
(0+)7\1
」與「0+70+
」不等價,比如後者可匹配「0070」而前者不能。 - 許多工具在取代文字時可引用子群組的值。例如對文字「xabycccdz」用「
x(a*b)y(c*d)z
」取代為「\1,\2
」會得到「ab,cccd」;若取代為「\2,\1
」則會得到「cccd,ab」。有些程式(如JavaScript)不是用「\1
」而是用「$1
」表示。有些程式還支援其他參數,例如「$&
」表示整個匹配字串。 - 括號可巢狀嵌套。例如對文字「xabbcy」用「
x(a(b+)c)y
」取代為「\1,\2
」會得到「abbc,bb」。 - 子群組可能得到空值,也可能不存在。例如用「
go(o*)gle
」匹配「gogle」後,第1子群組是空值;用「go(o)*gle
」匹配「gogle」後,第1子群組為不存在。有些程式能區分存在與不存在的子群組,進而設定不同的處理方式,例如Notepad++可以尋找文字「go(o)*gle
」取代為「(?1google:fake)
」,若輸入文字是「This is gooogle.
」會得到「This is google.」,若是「gogle
」則會得到「This is fake.」。
- 注意:子群組的值是x匹配到的值,而不是x表示式。「
- 「
(?<name>x)
」:將x視為子群組,同時把匹配到的值記錄在名為「name」的子群組,之後可用「\k<name>
」引用。例如「(?<zeros>0+)7\k<zeros>
」可匹配「070」、「00700」、「0007000」、……等等。 - 「
(?:x)
」:將x視為子群組,但不記住x的內容。例如「(?:ya)+
」可匹配「ya」、「yaya」、「yayaya」、……。 - 「
(?=x)
」:檢查此位置後面為x,x本身不納入匹配。例如用「予(?=.*?桂枝)(?=.*?麻黃)
」匹配「予桂枝、麻黃」或「予麻黃、桂枝」都會得到「予」,而匹配「予桂枝、芍藥」則無結果。 - 「
(?!x)
」:檢查此位置後面非x,x本身不納入匹配。例如用「予(?!.*?桂枝)(?!.*?麻黃)
」匹配「予桂枝、大棗」或「予麻黃、杏仁」都無結果,而匹配「予芍藥、生薑」會得到「予」。 - 「
(?<=x)
」:檢查此位置前面為x,x本身不納入匹配。例如用「(?<=曰)夫
」匹配「帝曰夫道者年皆百數能有子乎」會得到「夫」,而匹配「丈夫八歲腎氣實」則無結果。 - 「
(?<!x)
」:檢查此位置前面非x,x本身不納入匹配。例如用「(?<!白)附子
」匹配「用炮附子一錢」會得到「附子」,而匹配「用白附子一錢」則無結果。- 有些正規表示式引擎不支援此類逆向檢查語法,例如較舊版本瀏覽器的JavaScript;許多正規表示式引擎雖支援此語法,但x須為固定字元數,例如可使用「
(?<=[abc])
」或「(?<!小明|美華)
」,但不可使用「(?<=a*b)
」或「(?<!土匪|狗)
」。
- 有些正規表示式引擎不支援此類逆向檢查語法,例如較舊版本瀏覽器的JavaScript;許多正規表示式引擎雖支援此語法,但x須為固定字元數,例如可使用「
- 「
x|y
」:匹配x或y。例如「我(愛|喜歡)你
」可匹配「我愛你」或「我喜歡你」。- 若用「
小美|小美愛小明
」匹配「我發現小美愛小明」,結果是什麼?答案是視正規表示式引擎而定,多數正規表示式引擎是「循序型」,會取第一個匹配成功的選項。就此例言,當引擎檢查到「小美」前,發現「小美」可以匹配成功,就取「小美」作為結果並略過其他選項;同樣文字如用「小美愛小明|小美
」匹配,結果就會變成「小美愛小明」。但有少數正規表示式引擎是「貪婪型」,這種引擎會從匹配成功的選項中選擇最長的,因此結果會是「小美愛小明」。
- 若用「
- 「
\
」:對後一個字元做脫義處理,使其不被解釋為具有特殊意義。例如「\.
」表示「.」,「\\
」則表示「\」本身。- 若後一個字元不具特殊意義,一般是表示相同字元,例如「
\,
」表示「,」,「\人
」表示「人」;但「\
」接數字或字母可能另有特殊意義,詳見後述。
- 若後一個字元不具特殊意義,一般是表示相同字元,例如「
- 「
\d
」:匹配一個數字字元,相當於「[0-9]
」。 - 「
\D
」:匹配一個非\d的字元。(包括換行字元) - 「
\s
」:匹配一個空白或換行字元。 - 「
\S
」:匹配一個非\s的字元。 - 「
\n
」:匹配一個行尾字元(linefeed,U+000A)。 - 「
\r
」:匹配一個回車字元(carriage return,U+000D)。- Windows預設換行是「\r\n」,Linux預設換行是「\n」,Mac預設換行是「\r」。
- 「
\t
」:匹配一個表格字元(tab,U+0009)。 - 「
\xFF
」(FF為2位數的十六進位數字):匹配相應編碼的單個字元(ASCII碼或環境使用的編碼)。例如「\x00
」匹配空字元,「\x20
」匹配半形空白字元。 - 「
\uFFFF
」(FFFF為4位數的十六進位數字):匹配相應Unicode編碼的單個字元。例如「\u4E00
」匹配「一」(U+4E00)。- 此語法在不同平台有不同變形。上述是JavaScript的寫法,設定Unicode標幟後也可以使用「
\u{FFFF}
」或「\u{FFFFF}
」;其餘如Perl、PCRE、Boost使用「\x{F+}
」(任意位數的十六進位數字前後加花括號);Python使用「\uFFFF
」和「\UFFFFFFFF
」。
- 此語法在不同平台有不同變形。上述是JavaScript的寫法,設定Unicode標幟後也可以使用「
- 「
\p{...}
」:匹配一個指定Unicode字元集合內的字元。可用的指定方式可查閱文件。一些實用例子如下:- 「
\p{Letter}
」或「\p{L}
」:匹配任何語言的文字字元,例如漢字「一」、英文字母「a」、日文假名「あ」等等。 - 「
\p{Number}
」或「\p{N}
」:匹配數字字元,例如阿拉伯數字0-9、漢字零「〇」等等。 - 「
\p{Punctuation}
」或「\p{P}
」:匹配標點符號字元,例如「,」、「.」、「,」、「。」等等。
- 「
- 「
\P{...}
」:匹配一個非\p{...}的字元。 - 「
^
」:檢查此位置為字串開頭(或可設定為行首)。例如「^小明
」匹配字串開頭(或行首)的「小明」。 - 「
$
」:檢查此位置為字串結尾(或可設定為行尾)。例如「台灣。$
」匹配字串結尾(或行尾)的「台灣。」。 - 「
[...]
」:匹配一個字元集合中的字元,例如「x[abc]y
」可匹配「xay」、「xby」、「xcy」。- 「
[x-y]
」可指定字元區間。例如「[a-z]
」可匹配「a」、「b」、「c」、…、「z」。此語法可與單個字元混用,例如「[0-9A-Za-z_,]
」。 - 「
[^...]
」可反轉字元集合,例如「[^abc]
」表示匹配一個「a」、「b」、「c」以外的字元。 - 在「
[...]
」之中除「\
」、「]
」、開頭的「^
」、及可與前後字元形成區間的「-
」以外,任何字元皆無視特殊意義,例如「[.+^-]
」表示匹配「.」或「+」或「^」或「-」。
- 「
典籍檢索器無法支援的正規表示式
正規表示式極為彈性,由於技術限制,笈成典籍檢索器有些功能無法完全支援所有表示式。以下是一些已知的例子:
- 字元集合(
[...]
)不支援比對異體字。例如「乾[薑]
」表示搜尋「乾薑」,其中「乾」比對異體字,而「薑」不比對。這是因為像「(參|耆)
」可用程式替換為「([參参薓蔘]|[耆芪])
」,但「[參耆]
」、「[^參耆]
」則無法如此處理。如有比對異體字的需求,可用前者取代後者。 - 重複次數運算子(
{m,n}
、*
、+
、?
)不支援略過標點。例如「陰*
」無法匹配「陽氣根於陰,陰氣根於陽」。
應用實例
以下列舉一些可在本站典籍檢索器使用的正規表示式。注意實際使用時要加上「re:
」,且通常須把整個表示式加上雙引號,因為正規式示式的特殊字元通常在典籍檢索器也有特殊意義,會導致誤判。例如「re: "五(臟|藏)六(腑|府)"
」表示「含有『五(臟|藏)六(腑|府)』」,如果寫成「re: 五(臟|藏)六(腑|府)
」會被錯誤解讀成「含有『五』且含有『臟|藏』且含有『六』且含有『腑|府』」。
[被披][髮發][緩缓]形
:尋找「被髮緩形」的各種變形:第一字為「被」或「披」,第二字為「髮」或「發」,第三字為「緩」或「缓」,第四字為「形」。不考慮異體字。(披|被)(髮|發)(緩|缓)形
:同上,但各字皆考慮異體字。(?:披|被)(?:髮|發)(?:緩|缓)形
:同上,不引用子群組時,這種寫法不記錄子群組的值,可稍微提高執行速度,但寫起來稍麻煩些。(桂枝|麻黃)湯
:尋找「桂枝湯」或「麻黃湯」。(分支選擇的範圍越小,執行越快,此寫法比「(桂枝湯|麻黃湯)
」快,二者又比非正規表示式的「桂枝湯 OR 麻黃湯
」快。)麻黃(?!根)
:尋找「麻黃」,但排除「麻黃根」中的「麻黃」。(檢索「re: "麻黃(?!根)"
」與「麻黃 -麻黃根
」邏輯不同:諸如「麻黃發汗,麻黃根止汗」的文本在檢索前者時可找到,而在檢索後者時會被排除。)(?<!白)附子
:尋找「附子」,但排除「白附子」中的「附子」。桂枝(?:加.{0,8}?)?湯
:「桂枝湯」或「桂枝加…湯」(「…」長度8字以內)。此式可找到如「桂枝加桂湯」、「桂枝加龍骨牡蠣湯」等等。桂枝.{0,10}?麻黃.{0,10}?附子
:「桂枝」後10字內有「麻黃」,再後10字內有「附子」。此式可找到如「桂枝去芍藥加麻黃細辛附子湯方」。(?=桂枝|芍藥|生薑|甘草)(?=.{0,60}?桂枝)(?=.{0,60}?芍藥)(?=.{0,60}?生薑)(?=.{0,60}?甘草).{0,60}(?:桂枝|芍藥|生薑|甘草)
:60字範圍內含有「桂枝」、「芍藥」、「生薑」、「甘草」四個關鍵詞,順序不拘。此式可找到一些含有此四種中藥的方子。(?=.{0,15}?朴)(?=.{0,15}?參)(?=.{0,15}?薑)(?=.{0,15}?甘)(?=.{0,15}?半).{0,15}湯
:尋找某個由「朴」、「參」、「薑」、「甘」、「半」組成的湯方,各字順序不確定,長度15字以內。「[^」\n]*古方[^」\n]*」
:找含有「…古方…」的引言片段。
參考資源
正規表示式的參考資源極多,可自行尋找適當的學習資源。但要注意不同平台上的正規表示式語法稍有差異(一般稱作不同風味(flavor)),把一個平台的正規表示式搬到另一平台使用,往往須稍做調整。
例如JavaScript的正規表示式常寫成形如「/<div>.*?<\/div>/i
」,其中頭尾的「/
」是定界字元(delimiter),定界字元不是正規表示式的一部分,只是為了在程式語言中標示正規表示式的開始和結束,若正規表示式也會用到定界字元,則也必須做脫義處理,以免被誤判為結尾;結尾後的一或多個字母是調整正規表示式行為的標幟(flag),或稱修飾符號(modifier)。常見標幟如下:
- 「
i
」:無視大小寫。有此標幟時「a
」可以匹配「A」;否則不行。 - 「
m
」:多行模式。有此標幟時「^
」和「$
」表示行首和行尾;否則只表示字串開頭和結尾。 - 「
s
」:有此標幟時「.
」可匹配換行字元;否則不匹配。
如前所述,支援正規表示式的軟體工具很多,即使不寫程式,正規表示式也可用於文字資料的搜尋及取代,以簡化許多繁瑣的操作。以下是一些不必寫程式也能使用正規表示式的工具:
- 本站的典籍檢索器:支援用正規表示式搜尋文本,用的是瀏覽器腳本使用的JavaScript語言。
- Notepad++:國人製作的多功能純文字編輯軟體,可使用正規表示式尋找及取代文字,且支援多檔案操作。Notepad++使用的正規表示式是C++的Boost Regex Library。
- LibreOffice:自由且跨平台的辦公室軟體,可使用正規表示式尋找及取代文字。
- Google文件:Google文件和試算表都可以使用正規表示式尋找及取代文字(電腦版)。
- Regex101:此網站可輸入正規表示式及文字做尋找及取代,且會分析解釋輸入的正規表示式,並計算執行時間,既可應用亦可學習。可點擊正規表示式輸入區前後的「/」修改定界字元和標幟,點擊左上方的「☰」設定正規表示式的風味。
一些可參考的資料如下:
- 用十分鐘學會字串處理的那些事兒:陳鍾誠教授寫的入門教學。
- Regular-Expressions.info:介紹各種風味的正規表示式,及一些中階技巧。
- RexEgg:提供一些進階技巧,一般使用未必需要,但要寫出最佳化、高效能的正規表示式可以參考。
- Mastering Regular Expressions:聖經級的正規表示式教科書,從基本知識到高深技巧都有。網路上有免費版可供閱讀。