Share via


嗨,Scripting Guy!規則運算式不出錯

The Microsoft Scripting Guys

下載本文程式碼: HeyScriptingGuy2008_01.exe (150KB)

人非聖賢, 孰能無過。(不然您以為 Scripting Guys 小組是深思熟慮後成立的嗎?)不過,如果硬要找出犯錯的正面意義,就是 — 什麼?您以為我們會說犯錯的正面意義在於能夠從中學到教訓嗎?天啊,當然不是:您可以從犯錯中學到得唯一教訓就是:千金難買早知道。

註:抱歉;這次不開 Microsoft Bob 的玩笑了。我們曾經取笑過 Microsoft Bob 一次,我們錯了,而且也得到教訓了。只要事關 Microsoft Bob,我們絕對閉口不談。

坦白說,關於犯錯,Scripting Guy 實在想不到有什麼好話可說;因此我們終其一生都在避免犯錯 (一點也沒錯,我們避免犯錯的唯一方法,就是把實際要做的工作量減到最少。不過這已經離題,暫且不談)。

既然我們已經盡量減少犯錯,應該就天下太平;但是,萬一我們的同事繼續出錯,事情就不妙了。(別這樣,我們已經告訴您:不再開 Microsoft Bob 的玩笑了!)假如您有撰寫過資料庫程式或 Active Directory® 的前端 (相信大部分的讀者都寫過),那麼您一定懂我們的意思:您的前端資料輸入程式只適合讓使用者輸入資料。

假設您希望輸入的名字格式如下:Ken,也就是第一個字母大寫,其後的字母都小寫。萬一使用者輸入以下的名字,KEN 呢?。假設輸入的日期格式應該如下:01/23/2007。 萬一使用者輸入下面的日期,January 23, 2007 呢?假設 — 嗯,您能夠意會就好。我們說過,您的前端資料輸入程式只適合讓使用者輸入資料。但無論如何,即使是輸入資料,還是有人會弄錯。當然,除非您採取行動來確保別人不會犯錯。

確認資料有效

讓比賽落幕

您可曾想過 Scripting Guy 整年來最期待什麼事?如果過去兩年來,您曾在 2 月造訪指令碼中心,您可能會猜是「冬季指令碼比賽」。可惜,您錯了。Scripting Guy 最期待的是指令碼結束的時候,經過兩週不停的狂歡作樂之後,Scripting Guy 沐浴在又一次成功的「指令碼比賽」的光輝中,然後睡上一個月。

因此,為了迎接我們的年度盛事 — 「指令碼比賽」落幕時 — 該是時候展開「2008 冬季指令碼比賽」了。歡迎在 2008 年 2 月 15 日至 3 月 3 日造訪指令碼中心加入我們,同享兩週以上的指令碼歡樂時光,並體驗酷得不得了的指令碼大賽。

您可以在「指令碼比賽」好好測試和磨練指令碼技巧。而且今年 Scripting Guy 打算在比賽結束後多睡幾天,因此今年的比賽會比去年更盛大熱鬧!同樣地,我們將參賽者分成兩組:初學者與進階者。因此無論您是指令碼菜鳥,還是經驗老道 (或是年輕有為) 的專家,或是中等程度,都千萬別錯過這個比賽。

那麼今年有什麼新玩意兒?我們加入了另一種指令碼語言。您可以使用 VBScript、Windows PowerShell 或是 — 新傢伙登場 — Perl 來比賽 (我們知道 Perl 不是新的語言,但卻是這個比賽的新元素)。應該還有更多好玩的東西,不過既然我們得要在好幾個月之前就準備好這個資訊看板,所以還不曉得到底會有什麼。您必須到指令碼中心來親身體會我們為您設計的內容。

偷偷告訴您,沒錯,比賽會有獎品!至於獎品是什麼?嘿,我們才不要當掃興鬼 — 來指令碼中心瞧瞧您就知道了!microsoft.com/technet/scriptcenter/funzone/games/default.mspx

您們可能大多已經執行初步的資料輸入驗證了。而且在很多情況下,這樣簡單的資料輸入驗證便已綽綽有餘。需要確定字串值 strName 中是否包含 20 個以下的字元嗎?這一小段程式碼就可以做到:

If Len(strName) > 20 Then
    Wscript.Echo "Invalid name; a " & _
    "name must have 20 or fewer characters."
End If

但假如 strName 是一個新的零件編號,而零件編號必須符合特定模式:四個數字再加上兩個大寫字母 (例如 1234AB)。您可以使用基本的 VBScript 來驗證零件編號的 strName 是否遵守必要模式嗎?可以的。但相信我們,您不會想要這麼做。相反的,您該改用規則運算式。

確保值中只包含數字

規則運算式可追溯至 1950 年代,當時第一次將規則運算式描述為一種數學表示法形式。在 1960 年代,這個數學模型合併到 QED 文字編輯器中,成為在文字檔案中搜尋字元模式的方法。其後 — 您說什麼?喔,顯然不是每個人都跟我們一樣沈醉在規則運算式的歷史故事中。沒問題,那我們就廢話不多說,直接示範給您看囉。

想要確保變數 (在本例中,即 strSearchString 變數) 只包含數字嗎?[圖 1] 顯示方法之一。

Figure 1 Numbers only, please

Set objRegEx = _
  CreateObject("VBScript.RegExp")

objRegEx.Global = True   
objRegEx.Pattern = "\D"

strSearchString = "353627"

Set colMatches = _
  objRegEx.Execute(strSearchString)  

If colMatches.Count > 0 Then
    Wscript.Echo "Invalid: a " & _
      "non-numeric character was found."
Else
    Wscript.Echo "This is a valid entry."
End If

如您所見,指令碼一開始先建立 VBScript.RegExp 物件的執行個體,這個物件會 — 對,您說的沒錯,此物件可讓我們在指令碼中使用規則運算式 (唉呀,我們本來想自己解釋的)。接著,我們為規則運算式物件的以下兩個主要屬性指派值:Global 和 Pattern。

Global 屬性決定指令碼應否比對所有符合模式的項目,或者只要找到第一個項目就停止。一般而言,最好將這個值設為 True,表示您要搜尋所有符合模式的項目 (預設為 False)。通常您都會希望尋找所有符合模式的項目。即使您不想,只找第一個符合模式的項目的唯一好處就是如果指令碼未搜尋至字串的結尾就中途停止,可以加快執行的速度。理論上這樣還不錯。但實際上,搜尋作業往往很快就完成了,您甚至不會注意到其中的差別。

精彩的部份來了:Pattern 是我們指定所需字元 (和字元藍圖) 的地方。在範例指令碼中,我們要尋找不屬於 0 到 9 的數字的字元。如果找到這樣的字元 (可能是字母、標點符號等等),就表示指派給 strSearchString 的值無效。在規則運算式中,語法 \D 是用來比對任何非數字的字元,因此我們為 Pattern 屬性指派值 \D。

註:我們怎麼知道 \D 會比對非數字字元?這是有緣故的,說來話長。好多年前 — 喔,算了。原因是我們在 MSDN® (msdn2.microsoft.com/1400241x) 的 VBScript 語言參考中查到這個用法。

指派 Global 與 Pattern 屬性的值之後,下一步就是指派 strSearchString 變數的值:

strSearchString = "353627"

那麼,我們怎麼知道這個字串中是否有任何非數字字元?很簡單。我們只要呼叫 Execute 方法,在我們的變數中搜尋非數字字元就成了:

Set colMatches = _
objRegEx.Execute(strSearchString)

當您呼叫 Execute 方法時,所找到的任何符合項目 — 也就是 Pattern 的任何執行個體 — 會自動儲存在 Matches 集合中 (在我們的範例指令碼中,將此集合命名為 colMatches)。如果想知道我們的變數是否包含非數字字元 (我們的確想知道),現在只需要檢查集合的 Count 屬性值,這個值便會透露集合中有多少項目。

If colMatches.Count > 0 Then
    Wscript.Echo "Invalid: a " & _
      "non-numeric character was found."
Else
    Wscript.Echo "This is a valid entry."
End If

如果 Count 大於 0,就只代表一件事:變數值中找到非數字字元 (如果變數值中的所有字元都是數字,那麼集合應該是空的,而 Count 則為 0)。在範例指令碼中,我們接著回應發現非數值字元。在實際的指令碼或資料庫前端中,您可能會回應類似的訊息,並重返迴圈讓使用者再試一次。但這點就勞您自個兒費心了;因為我們 Scripting Guy 還有很多其他事要操煩。

例如什麼事呢?例如一想到《TechNet Magazine》的同仁哪天可能真的讀起我們每月交送給他們的文章。我們倒寧願他們不會從錯誤中學習教訓。

問得好:我們為何不直接使用 IsNumeric 函式來確定 strSearchString 是否包含數字?只要 strSearchString 可以接受任何數字的話,當然沒問題。但萬一 strSearchString 必須是正整數呢?這樣可能會出問題;因為 IsNumeric 會將以下兩者都視為有效數字:

-45
672.69

為什麼它們都被視為有效數字?因為它們的確是有效數字;只不過並非正整數罷了。不過我們的規則運算式會將這些數字標記為無效輸入,因為規則運算式可捕捉到非數字字元減號 (–) 與句號 (.)。如果您正一邊閱讀這篇專欄,一面自問「我何必看這篇專欄?」您看,上述內容不正好解答您的疑惑。

您說什麼?這個理由不夠充分?哇!本月的讀者還真難纏。好吧,我們再來看一下,使用規則運算式還可以完成其他哪些類型的資料輸入驗證。

確保值中沒有數字

我們剛才示範如何確保值中只包含數字。如果您想要反過來,變成要確保值中沒有數字,該怎麼做?我們可以使用下面這個搜尋模式:

objRegEx.Pattern = "[0-9]"

這是怎麼一回事?在規則運算式這個千奇百怪的世界中,方括弧字元 ([ 與 ]) 可讓您指定一組字元,或像上面一樣指定一個字元範圍。Pattern [0-9] 表示什麼意思?這表示我們要搜尋從 0 到 9 這個範圍以內的字元 — 換句話說,就是任何數字值。如果只需要偶數 (也就是說,如果我們要確保值中不包含 1、3、5、7 或 9),可以使用以下這個模式:

objRegEx.Pattern = "[13579]"

要注意的是,我們並未使用連字號,因此並非尋找字元範圍。事實上,我們要找的是明確的個別數字 1、3、5、7 及 9。

當然,模式 [0-9] 只尋找數字;它不會尋找既非字母也非數字的標點符號或其他字元。您認為我們可以建立一個模式來尋找非字母的字元嗎?當然沒問題。使用規則運算式幾乎可以實現您的任何想法:

objRegEx.Pattern = "[^A-Z][^a-z]"

在此,我們其實是在模式中結合兩個準則:我們要搜尋 [^A-Z] 與 [^a-z]。您可能已經猜到,A-Z 指的是從大寫 A 到大寫 Z 的字元範圍。但 ^ 字元代表什麼意思?當包含在一對方括弧內時,^ 就表示:「尋找不包含在此字元集中的任何字元」。因此,[^A-Z] 表示要「尋找任何不屬於大寫字母的字元」。在此同時,[^a-z] 則表示:「尋找任何不屬於小寫字母的字元」。結果:這個模式表示我們要尋找並非大寫字母或小寫字母的項目。其中包括數字、標點符號,以及其他可以在鍵盤上找到的奇怪字元。

或者,我們可以將 IgnoreCase 屬性設為 True:

objRegEx.IgnoreCase = True

藉由這麼做,我們的搜尋便會忽略字母大小寫。如此一來,我們接著可以使用以下模式 (並與 IgnoreCase 屬性結合) 來尋找大寫字母或小寫字母以外的字元:

objRegEx.Pattern = "[^A-Z]"

確保零件編號有效

現在我們來耍點花招 (別懷疑,使用規則運算式可以變出很多把戲;您可以從網站 regexlib.com 中找到一些例子)。在前文中我們曾提到:假設 strName 是一個新的零件編號,而零件編號必須符合特定模式:四個數字再加上兩個大寫字母 (例如,1234AB)。您要如何驗證變數是否符合這個模式?不用說,當然是這麼做囉:

objRegEx.Pattern = "^\d{4}[A-Z]{2}"

這個模式代表什麼意思?說來真巧:我們剛好要解釋這個模式的意思。在這個範例中,我們有三個要搜尋的準則:

^\d{4} 

\d 表示我們要尋找數字 (範圍從 0 到 9 的字元)。{4} 代表什麼意思?這代表我們要比對恰好四個字元 (或者說,恰好四個數字)。因此我們要找的是四個連續數字。

那 ^ 字元又作何解釋呢?出現在一對方括弧外面時,^ 是指值必須以隨後的模式開始,也就是說,AA1234AB 將不會被標記為符合項目 (原因何在?因為這個值實際上是以 AA 開始,而非四個連續數字)。如有必要,我們也可以使用 $ 字元來指定符合項目必須出現在字串結尾。但是我們覺得沒有必要 (至少這個範例還用不著)。

[A-Z]{2} 

您已經知道 [A-Z] 元件的意義;代表任何大寫字母。那麼 {2} 呢?沒錯:四個數字之後必須加上恰好兩個大寫字元。

怎樣,很簡單吧!順道一提,在這個範例中,如果 Count 為 0,表示值無效。為什麼呢? 這次我們不是要搜尋讓字串失效的單一字元;而是要搜尋準確的模式比對。如果找不到相符項目,就表示我們的值無效。而且也表示集合的 Count 屬性將是 0。

請注意:另外兩個有用的語法結構分別為:{3,} 和 {3,7}。{3,} 表示至少必須有 3 個連續字元 (或運算式);不過,雖然下限為 3,卻沒有任何上限。此 \d{3,} 與 123、1234 以及 123456789 都相符。{3,7} 表示至少必須有 3 個連續字元 (或運算式),但不能超過 7 個。因此 123456 是相符項目,但 123456789 (有超過 7 個連續數字) 就非相符項目。

另外還有一個好用的模式。假設您的零件編號開頭是 4 個數字,緊接著是字母 US,之後再加上最後兩個字元 (可以是字母、數字或任何字元)。進行比對的其中一個方法如下:

objRegEx.Pattern = "^\d{4}US.."

如您所見,這個模式開頭是四個數字 (\d),後面必須連接字元 US (而且只能是字元 US;否則就不相符)。在字元 US 之後,我們還需要兩個某種類型的字元。我們如何在規則運算式中表示任意字元?等一下;我們知道的 ... 有了,想到了:任意的單一字元是以句號 (.) 表示。因此我們可以合理推斷,兩個字元可以用兩個句號來指示:

..

好吧,這個例子很簡單。我們來挑戰難一點的。假設裡面的這兩個字元指出零件的製造國家。如果您在美國、英國和西班牙製造這些零件,就表示下列兩個字元為一組的代碼均有效:US、UK、ES。我們到底該如何在規則運算式中允許多重選擇?

您覺得這麼做如何:

objRegEx.Pattern = "^\d{4}(US|UK|ES).."

此處的關鍵就在於這個小小的語法結構:(US|UK|ES)。我們將這三組可接受的值 (US、UK 和 ES) 放入括弧內,並以分隔線 (|) 字元分隔這些值。就是這個方法可讓您在規則運算式中使用多重選擇。

不過先別急:我們剛才不是說,您可以在方括弧內放入多個選項?整個 [13579] 的用意不就在此嗎?是的。然而,那只適用於單一字元;[13579] 一定會被解譯為 1、3、5、7 和 9。若要使用 135 和 79 來當做選項,就必須使用上述語法:(135|79)。 您也可以使用括弧來界定要搜尋的單一字詞:

objRegEx.Pattern = "(scripting)"

或者,直接丟掉括弧:

objRegEx.Pattern = "scripting"

請注意:沒錯,這一點很重要,但萬一括弧是搜尋字詞的一部份而必須包含在內,比方說,如果我要搜尋 (scripting),該怎麼辦?由於左右括弧都是規則運算式中的保留字元,因此您必須在每個字元前面加上 \。也就是:

objRegEx.Pattern = "\(scripting\)"

自由自在的運算式

本月專欄也接近尾聲了。如果您想進一步了解規則運算式,不妨參考網路廣播《系統管理員的字串理論:規則運算式簡介》(microsoft.com/technet/scriptcenter/webcasts/archive.mspx)。當您開始學習及使用規則運算式時,請謹記蘇菲亞羅蘭的名言:「過錯是充實的人生所需償還的債」。

哇!原來 Scripting Guy 的人生比我們所想的還要充實!

Dr. Scripto 的指令碼謎題 (Scripting Perplexer)

這個每月一次的挑戰不僅測試您的解謎功力,更要測試您的指令碼技巧。

2008 年 1 月:捲曲的名詞

本月,Dr. Scripto 決定要採用 Windows PowerShell。當然,少了指令程式碼就無法使用 Windows PowerShell — 如果您不是正確使用它的話 (Dr. Scripto 總是正確使用所有東西)。在這個猜謎中,Dr. Scripto 在格子中纏繞了一些指令程式碼,需要您去找出來。

沒錯,我們承認若要使用整個指令程式碼會使得此猜謎太複雜。基於指令程式碼的動詞與名詞建構,很多指令程式碼都以 Get-、Set- 等開頭。因此我們省略了指令程式碼的動詞部分 (還有連字號),只留下名詞 (不過告訴您,所有動詞都是「Get-」)。

您本月的任務就是解開這些指令程式碼名詞。我們已經提供您一份清單,列出隱藏在猜謎中的名詞。(我們有夠大方吧!)每個字都可能以縱橫或前後的方式捲繞,但不會是斜角的方式。我們提供您第一個字 (Location) 當做範例。祝您好運。

字詞清單

Alias
ChildItem
Credential
Date
EventLog
ExecutionPolicy
Location
Member
PSSnapin
TraceSource
Unique
Variable
WMIObject

ANSWER:

Dr. Scripto 的指令碼謎題 (Scripting Perplexer)

答案是:捲曲的名詞,2008 年 1 月

The Microsoft Scripting GuysScripting Guy 為 Microsoft 做事,也就是受雇於 Microsoft。他們在不玩、不教或不看棒球 (以及其他各種活動) 的時候,就負責管理 TechNet 指令碼中心。請造訪他們的網站:www.scriptingguys.com

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