如果說有什麼工具是在 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 比對「不包含」的語法介紹給大家,如果大家還有其他解法,也請不吝分享給我。