[料理佳餚] Regular Expression(正則表達式)的比對「不包含」

如果說有什麼工具是在 IT 行業內不同領域都需要去學會的,我認為 Regular Expression(正則表達式)會是其中之一,而我們使用 Regular Expression 大都是去比對是否包含某個字或詞?鮮少遇到比對「不包含」的情境,我就我遇過的需求來介紹 Regular Expression 的比對「不包含」。

排除(Exclusion)

排除語法([^])算是在 Regular Expression 裡面,大家比較知道的其中一種比對「不包含」的語法,我比較常用來找兩個標記符號中間的任意字元,比如說,我要找兩個括號中間的任意文字,比對模式就會是 \(([^)]+)\)

不過,排除語法只能排除單一個字元,排除字詞就不好辦到,要排除字詞的話,平常我們多寫一些程式也就處理掉了,比如說,給我們一堆台北市的地址,要我們濾掉在忠孝東路七段的地址,這時候我們可能會先找出在忠孝東路七段的地址,然後做第二次處理,從集合中把它濾掉,也就搞定了。

但是,有時候我們只有一次處理的機會,接下來就是我真實遇到的案例,ASP.NET Core 中的 Rewrite Middleware 擴充方法,它的參數就要我們丟 Regular Expression Replacement 語法進去,表示說在網址符合比對模式的當下,馬上就在 Rewrite Middleware 裡面按照 Replacement 的規則被置換掉了,一氣呵成,在這裡我們比對模式就要寫得精準。

我們有一個服務,這個服務的網址是這樣的 /xxx/yyy/{zzz},這個 {zzz} 是參數,根據 {zzz} 的不同會回傳不同內容,有一天 /xxx/yyy 要搬家了,以後 /xxx/yyy 要換成 /aaa/bbb,那我選擇在後端用 Rewrite Middleware 寫一個 Redirect 處理掉,但是如果 {zzz} 的值是 cde 的時候,不能被轉址,因為某種原因 /aaa/bbb/cde 還沒辦法搬過去。

因此比對模式的邏輯就變成「非 /aaa/bbb/cde 網址的開頭全替換成 /xxx/yyy/」,找出 /aaa/bbb/{zzz},但 {zzz} 不包含 cde,下面就輪到我們今天的主角登場。

…不在…的前面(Negative Lookahead)

在 Regular Expression 中有一組 Lookaround 語法,Negative Lookahead (?! ) 便是其中之一,它的用法是這樣的:

abc(?!cde)

它的英文名稱雖然是 Lookahead,但是在使用的時候卻是放到目標比對文字的後面,這點讓我當初在理解它的時候卡了一段時間,後來才知道 Lookahead 是它在 Regular Expression 測試條件的順序,不過我自己還是解讀成「…不在…的前面」,以剛剛的使用範例來講,就是「abc 不在 cde 的前面」,這個語法就能用來解決我遇到的問題「/aaa/bbb/ 不在 cde 的前面就替換成 /xxx/yyy/」。

Regular Expression 是一個讓人又愛又恨的東西,語法看起來像天書,後續維護起來也是如臨深淵、如履薄冰,但是不得不讚嘆這個工具的強大,很多迂迴的解法,往往一句 Regular Expression 就搞定了。

其他 Lookaround 語法

Lookaround 語法還有其他三個,使用方法大同小異,差別就只是在目標比對文字的前面或後面,以及擺放的位置:

…不在…的後面(Negative Lookbehind)

abc 不在 cde 的後面:

(?<!cde)abc

…在…的前面(Lookahead)

abc 在 cde 的前面:

abc(?=cde)

…在…的後面(Lookbehind)

abc 在 cde 的後面:

(?<=cde)abc

Regular Expression 了解的語法愈多,能變化的寫法就愈多,像文章開頭提到的範例「找兩個標記符號中間的任意字元」,我們就能用 Lookahead 加上 Lookbehind 來寫:

(?<=\().+(?=\))

以上,我就我所知道的 Regular Expression 比對「不包含」的語法介紹給大家,如果大家還有其他解法,也請不吝分享給我。

參考資料

C# 指南ASP.NET 教學ASP.NET MVC 指引
Azure SQL Database 教學SQL Server 教學Xamarin.Forms 教學