Windows PowerShell撰寫規則運算式

Don Jones

192.168.4.5,\\Server57\Share,johnd@contoso.com,不用說,您一定認得出這三個項目分別是 IP 位址、通用命名慣例 (UNC) 路徑,以及電子郵件地址。您的大腦辨識出它們的格式,四組數字、反斜線、@ 符號,以及其他線索指出了這些字元所代表的

資料類型。不用多想,您很快就能辨識出 192.168 本身並不是有效的 IP 位址,7\\Server2\\Share 不是有效的 UNC,還有 joe@contoso 不是有效的電子郵件地址。

不幸的是,電腦要下更多的功夫,才能了解這類的複雜格式,而此時規則運算式就派上用處了。規則運算式是使用特殊的規則運算語言寫成的字串,它可幫助電腦識別特定格式的字串 — 例如 IP 位址、UNC,或是電子郵件地址。撰寫得當的規則運算式能夠讓 Windows PowerShellTM 指令碼接受為有效資料,或將不符合您指定格式的資料拒絕為無效。

進行簡單的比對

Windows PowerShell –match 運算子會將字串與規則運算式 (或稱 regex) 相比較,然後根據字串是否符合 regex 傳回 True 或 False。一個極為簡單的 regex 甚至根本不需要包含任何特殊語法 — 文字字元就綽綽有餘。例如:

"Microsoft" –match "soft"
"Software" –match "soft"
"Computers" –match "soft"

在 Windows PowerShell 中執行時,前兩個運算式會傳回 True,而第三個運算式會傳回 False。每個當中的字串後面都會加上 –match 運算子,隨後再加上 regex。根據預設,regex 會在字串間浮動以尋找相符的項目。Software 和 Microsoft 中同時可以找到「soft」字元,但位於不同的位置。另外也要注意的是,regex 依預設不區分大小寫 — 「Software」中的 S 儘管是大寫,還是在當中找到「soft」。

但是如果必要的話,可使用不同的運算子 –cmatch,它提供有區分大小寫的 regex 比較,如下所示:

"Software" –cmatch "soft"

由於「soft」字串在區分大小寫的比較當中與「Software」不符,因此這個運算式傳回 False。請注意,儘管 –match 的預設行為是不分大小寫,您還是可以使用 –imatch 運算子作為明確不分大小寫的選項。

萬用字元和重複項

Regex 可包含數個萬用字元,舉例來說,句點對應任何字元的一個例項。而問號則對應任何字元的零或一個例項。下面有幾個說明的範例:

"Don" –match "D.n" (True)
"Dn" –match "D.n" (False)
"Don" –match "D?n" (True)
"Dn" –match "D?n" (True)

在第一個運算式中,句點代表單獨一個字元,所以比對為 True。在第二個運算式中,句點找不到可以包含它的字元,所以比對為 False。如第三和第四個運算式所示,問號可以對應單一未知的字元或完全不對應任何字元。最後,在第四個範例中,找到「D」和「n」因為它們之間沒有任何字元,所以比對為 True。因此,問號可以想成是代表選用字元,所以即使該位置中沒有出現任何字元,比對還是 True。

Regex 也可以將 * 和 + 符號視為重複項。不過這些需要跟隨在一些字元後面。* 對應零或更多的指定字元,而 + 符號則對應一或多個指定字元。這裡列出一些範例:

"DoDon" –match "Do*n" (True)
"Dn" -match "Do*n" (True)
"DoDon" -match "Do+n" (True)
"Dn" -match "Do+n" (False)

請注意 * 和 + 兩者都比對「Do」,而不只是「o」。這是因為這些重複項是針對一連串字元,而不只是一個字元而設計的。

要是您需要對應句點、*、? 或 + 符號本身呢?只要在它們前面加上反斜線就行了,這是 regex 的逸出字元:

"D.n" -match "D\.n" (True)

請注意,這跟 Windows PowerShell 的逸出字元 (反向簡縮號) 不同,但遵循的是業界標準的 regex 語法。

字元類別

字元類別是範圍更廣的萬用字元,代表著整個字元群組。Windows PowerShell 可識別出好幾種字元類別,例如:

  • \w 對應任何文字字元,包括字母和數字。
  • \s 對應任何空白字元,例如定位點、空間等。
  • \d 對應任何數字字元。

另外還有否定的字元類別:\W 對應任何非文字字元,\S 對應非空白字元,還有 \D 對應非數字字元。這些類別的後面可以加上 * 或 + 來表示可以接受多重符合項。這裡列出一些範例:

"Shell" -match "\w" (True)
"Shell" -match "\w*" (True)

本月 Cmdlet

Write-Debug Cmdlet 用在寫入物件 (例如文字字串) 至 Debug 管線時相當實用。不過將這個 Cmdlet 用在命令介面中時可能會讓您有點失望,因為它看起來無所事事。

問題出在 Debug 管線預設是關閉的 — $DebugPreference 變數是設定為 "SilentlyContinue"。不過將它設為 "Continue",您使用 Write-Debug 傳送的所有內容就會以黃色文字顯示在主控台上。這是在指令碼中新增追蹤程式碼的最佳方法,讓您能夠遵循複雜指令碼的執行動向。黃色有助您將追蹤行跡和指令碼的正常輸出相區別,並且您可以隨時關閉偵錯訊息,而不需要移除所有的 Write-Debug 陳述式。只要再次設定 $DebugPreference = "SilentlyContinue",偵錯文字就會隱藏起來。

雖然兩種運算式都會傳回 True,但它們比對的內容是天差地別。所幸,有種方法可以看出 –match 運算子骨子裡到底在打什麼主意:每次找出相符的項目,符合的結果中 — 也就是運算子針對您的 regex 在字串中找到的任何字元 — 會隨著填入一個稱為 $matches 的特殊變數。$matches 變數會保留它的結果,直到使用 –match 運算子進行另一次比對符合為止。[圖 1] 說明剛剛提到的兩個運算式之間的差別。如您所見,\w 對應到「Shell」中的「S」,而重複的 \w* 對應到整個字。

[圖 1] 用不用 * 的差別有多大

[圖 1]** 用不用 * 的差別有多大 **(按影像可放大)

字元群組、範圍和大小

Regex 也可以包含字元群組或字元範圍,以方括弧括住。例如,[aeiou] 表示當中任何一個內含的字元 — a、e、i、o 或 u — 都算是相符項目。[a-zA-Z] 指出介於範圍 a-z 或 A-Z 間的任何字母都可接受 (不過假如您是使用不分大小寫的 –match 運算子,那麼只要使用 a-z 或 A-Z 就夠了)。請看以下範例:

"Jeff" -match "J[aeiou]ff" (True)
"Jeeeeeeeeeeff" -match "J[aeiou]ff" (False)

您也可以使用大括弧來指定字元的最小和最大數量。{3} 表示您只想要三個指定的字元,{3,} 意謂您想要至少三個或以上,而 {3,4} 則表示您想要至少三個但不超過四個。這是為 IP 位址建立 regex 的理想方法:

"192.168.15.20" -match "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" (True)

這個 regex 想要四組的數字,每個各含一到三個數字,全都以文字句點分隔。但是先考慮一下這個範例:

"300.168.15.20" -match "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" (True)

這顯示出 regex 的限制,雖然此字串的格式看起來像 IP 位址,但它顯然不是有效的 IP 位址。Regex 不能判斷資料是否真的有效,它只能判別資料看起來對不對。

停止浮動

疑難排解 regex 的問題可能需要點技巧。舉例來說,這裡有一個以 \\Server2\Share 格式來測試 UNC 路徑的 regex:

"\\Server2\Share" -match "\\\\\w+\\\w+" (True)

這裡的 regex 本身並不容易閱讀,因為我想要測試的每個文字反斜線都必須使用第二個反斜線逸出。這看起來似乎沒問題,其實不然:

"57\\Server2\Share" -match "\\\\\w+\\\w+" (True)

第二個範例很清楚地 (至少對您對我來說) 不是有效的 UNC 路徑,但是 regex 卻舉旗通過。為什麼呢?記得 regex 預設會浮動嗎?這個 regex 只是尋找兩個反斜線,一或多個字母和數字,另外一個反斜線,以及更多字母和數字。字串中反覆著這種模式 — 加上前面有多餘的數字,使它成為無效的 UNC。訣竅是告訴 regex 從字串的開頭開始比對,而不要浮動。我像下面這麼做來達到這個目的:

"57\\Server2\Share" -match "^\\\\\w+\\\w+" (False)

^ 字元指出這是字串開始的位置。加上這個字元,這個無效的 UNC 路徑就不符合條件,因為 regex 現在會尋找前兩個字元必須是反斜線,而在此例中,它們並不是。

同樣地,$ 符號可用來表示字串的結尾。這在 UNC 路徑的例子不是很有用,因為 UNC 路徑可以包含額外的路徑區段,比方說 \\Server2\Share\Folder\File。不過,我確定您想要指定字串結尾的情況多得很。

提供規則運算式的協助

在 Windows PowerShell 中,about_regular_expressions 說明主題提供了 regex 語法的基本語法協助,但是線上資源提供的資訊更多更豐富。譬如,我最喜愛的網站之一,www.RegExLib.com,提供了免費的規則運算式程式庫,這些針對不同目的寫成,而且是由同好者貢獻的。您可以搜尋關鍵字,例如「e-mail」或「UNC」,迅速找出符合您需要的 regex — 或者至少提供一個不錯的出發點。如果您恰好建立了一個很棒的 regex,可以將它貢獻到程式庫,讓其他人也可以利用。

我也蠻喜歡 RegexBuddy 的 (www.RegexBuddy.com),這是一個花費不多的工具,提供圖形化的 regex 編輯器。RegexBuddy 使複雜的 regex 組合起來更加容易,這項工具也讓測試 regex 更加簡單,確定它確實接受有效的字串並拒絕無效的字串。還有許多其他軟體開發人員也建立了免費的、分享的及商用的 regex 編輯器和測試工具,使用者肯定會覺得實用。

使用規則運算式

您可能會質疑在真實生活中怎麼用得到 regex。想像您正從 CSV 檔案讀取資訊,然後使用該資訊在 Active Directory® 中建立新使用者。如果 CSV 檔案是由別人產生的,您會想要驗證當中資料的正確性。而 regex 正適合執行這類工作。舉例來說,一個像是 \w+ 的簡單 regex,可以確認姓氏和名字沒有包含任何特殊的字元或符號,而比較複雜的 regex,則可以確認電子郵件地址符合您的公司標準。例如,您可以使用:

"^[a-z]+\.[a-z]+@contoso.com$"

這個 regex 要求電子郵件地址必須是 don.jones@contoso.com 的格式,當中姓氏和名字只能包含字母,而且它們必須以句點分隔。順帶一提,寫電子郵件地址的 regex 很麻煩。如果您可以將範圍縮小到特定的公司標準,就比較省事。

別忘了那些開始和結束錨點 (^ 和 $),它們可確保 contoso.com 後面沒有任何字元,還有組成使用者姓氏的字元前面也沒有其他字元。

在 Windows PowerShell 中實際運用這個 regex 相當簡單。假設變數 $email 包含您從 CSV 檔案讀取的電子郵件地址,像下面這樣的內容可以檢查它是否有效:

$regex = "^[a-z]+\.[a-z]+@contoso.com$"
If ($email –notmatch $regex) {
  Write-Error "Invalid e-mail address $email" 
}

您在這個範例中學到了一個新運算子,如果字串不符合提供的 regex,-notmatch 會傳回 True (另外有一個 –cnotmatch 用於區分大小寫的比較)。

關於規則運算式,我在這裡還有很多都沒有提到 — 其他字元類別,更進階的作業,其他運算子。另外還有 Windows PowerShell 支援的 [regex] 物件類型。不過,這裡大略介紹的 regex 語法應該就足以讓您動手試試。如果您需要幫忙解決特別麻煩的 regex 難題,歡迎您隨時到 www.ScriptingAnswers.com 來拜訪我。

Don Jones 是《Technet Magzine》的特約編輯,也是《Windows PowerShell:TFM》(SAPIEN Press) 的作者之一。他教授 Windows PowerShell (www.ScriptingTraining.com),您可以透過 ScriptingAnswers.com 網站與他連絡。

© 2008 Microsoft Corporation and CMP Media, LLC. 保留所有權利;未經允許,嚴禁部分或全部複製.