Scripting Guy 為您解答問題

Hey, Scripting Guy!

歡迎使用 TechNet 專欄,Microsoft Scripting Guy 會在此為您解答有關系統管理指令碼的常見問題。您有關於系統管理指令碼方面的問題嗎?請將電子郵件傳送到 scripter@microsoft.com。我們無法保證能夠逐一回答每個問題,不過我們會盡力而為。

重要資源

如何擷取字串中的三位數數字?

Hey, Scripting Guy! Question

嗨,Scripting Guy!我嘗試使用規則運算式來擷取縱線字元 (|) 間的三位數數字,這些數字像這樣內嵌在字串值中:|159|468|572|843|。 但我找不到可以做到這件事的語法。可以幫助我嗎?

        -- RS

Hey, Scripting Guy! Answer

嘿,RS:在回答這個問題之前,您應該已經注意到 2008 冬季指令碼比賽正如火如荼的進行中:不只所有主要比賽已經在星期五開始了,今天我們還在死亡挑戰張貼了第一個項目。不過,如果您現在才要加入這個比賽,那請放輕鬆吧:項目 1 和 2 的截止日期還有兩天 (2 月 20 日,星期三),而項目 3 和 4 的截止日期則還有四天 (2 月 22 日,星期五)。也就是說,不要緊張,您還有很多時間可以完成所有項目。

注意:唯一需要緊張的人應該是 Scripting Guy。雖然比賽只開始了幾天,而且還選在「指令碼中心」通常工作量比較少的星期五開始,但就我們看來,今年的參與人數比起去年更熱烈。這有什麼問題嗎?我們這樣來看好了:去年,我們差一點無法處理完所有提交的指令碼。那今年呢?喔,我們不太想討論今年的狀況。

不是因為我們不想討論今年的狀況,而是我們根本沒有時間可以討論。今天才星期一,而我們已經累攤了!

雖然 Scripting Guy 已經開始手忙腳亂了,但指令碼比賽的參賽者仍有非常充裕的時間。你們可以稍微休息一下,先來學學如何從字串中擷取三位數數字嗎?我們馬上就會知道你們的答案了,是吧:

Script Center
Set objRegEx = CreateObject("VBScript.RegExp")

objRegEx.Global = True
objRegEx.Pattern = "\d{3}"

strSearchString = "|159|468|572|843|"

Set colMatches = objRegEx.Execute(strSearchString)

For Each strMatch in colMatches
Wscript.Echo strMatch.Value
Next

RS,看來您把問題想得太複雜了(請相信我們,如果說有誰會發現簡單的問題被複雜化了,那個人一定就是撰寫此專欄的 Scripting Guy 了)。您嘗試在您的規則運算式中包含縱線字元 (|)。這有什麼問題嗎?確實有問題,因為縱線字元是規則運算式的保留字元;這意謂著您不能只是將它包括在 Pattern 中。您會發現,下列規則運算式模式不會傳回預期的資料:

objRegEx.Pattern = "|...|"

結果是您根本不需要擔心縱線字元。根據您提供的資料,您實際上要尋找的是三位數數字,與縱線字元無關。這些三位數數字可能被空白、逗號或甚至任何中間字元分隔:

aaaaa159bbbbb468ccccc572ddddd843eeeee

換句話說,我們要做的就是搜尋三位數數字,然後就無事一身輕了。

**注意:**技術上而言,我們甚至不需要規則運算式;我們只要利用 Split 函數,並於字串的縱線字元處分割即可。那我們為什麼不這樣做呢?如果那樣做,我們最後會獲得開始和結尾都是空項目的陣列。為了不要處理這種情況,我們決定只要修改 RS 的規則運算式指令碼。

那麼,我們該如何搜尋三位數數字呢?我們在第 1 行建立 VBScript.RegExp 物件的例項。該物件可讓我們在 VBScript 指令碼中使用規則運算式。建立 RegExp 物件之後,我們將值指定給物件的兩個主要屬性:

  • Global。Global 屬性決定指令碼只要尋找目標文字中的一個例項,或是目標文字中的「所有」例項。因為我們要尋找所有的三位數數字,所以將此屬性值設定為 True。

  • Pattern。Pattern 代表目標文字。我們會在其中指定要尋找的內容。我們要尋找三位數數字,所以我們使用這個語法:\d{3}\d 表示我們要尋找數字 (0 和 9 之間的數字)。我們必須使用 \,因為 d 也是規則運算式的保留字元。同時 {3} 會告訴指令碼,我們要比對這些字元 (介於 0 和 9 之間的數字) 中的三個,不多也不少。

將搜尋字串指定給名稱為 strSearchString 的變數之後,我們再叫用 Execute 方法來搜尋目標文字中的字串:

Set colMatches = objRegEx.Execute(strSearchString)

RegExp 物件找到的任何符合項目都會儲存在名稱為 colMatches 的集合中。若要顯示三位數數字的清單,我們只要設定 For Each 迴圈在集合中的項目中循環,並讓每個項目回應 Value 屬性即可:

For Each strMatch in colMatches
Wscript.Echo strMatch.Value
Next

這樣做之後,我們應該可以得到下列結果:

159
468
572
843

這就是我們要的東西。

不過,我們必須注意這個規則運算式是為了符合 RS 的資料所設計的,它可能無法與「任何」資料搭配使用。例如,假設我們的字串如下:

123aaaa|159|468|572|843|aaaa456

此字串將會正確找到數值 159、468、572 以及 843。不過,它也會找出數值 123 和 456。為什麼呢?因為它會搜尋「任何」三位數數字,不論是否被縱線字元包夾。

老實說,我們可以繼續建立更複雜的案例,而越複雜的案例需要的規則運算式模式就會更複雜。今天我們就讓您知道如何處理這類特殊問題就好。下面的指令碼只會擷取出「開頭」為縱線字元的三位數數字:

Set objRegEx = CreateObject("VBScript.RegExp")

objRegEx.Global = True
objRegEx.Pattern = "\|\d{3}"

strSearchString = "123aaaa|159|468|572|843|aaaa456"

Set colMatches = objRegEx.Execute(strSearchString)

For Each strMatch in colMatches
strValue = strMatch.Value
strValue = Replace(strValue, "|", "")
Wscript.Echo strValue
Next

為了讓這個指令碼可以運作,我們必須對原始的指令碼做部分修改。因為我們變更了 Pattern,如下:

objRegEx.Pattern = "\|\d{3}"

我們仍在尋找三位數數字。不過,該三位數數字的前面必須有 | 字元(就像之前說過的,因為縱線是保留字元,所以它必須要逸出,也就是在它前面放一個 \)。使用這個規則運算式,我們就不會找到字串開頭的數字 123。為什麼不會呢?您答對了:因為那個三位數數字前面沒有縱線字元。

下面是個好問題:那麼,搜尋前後都有縱線字元的三位數數值不是更正確?是的,的確。不過這樣會帶來新的問題。假設我們比對這個值 |159|。這不會有什麼問題,只是尾端的 | 已經被使用了。所以這表示我們將不會比對 |468|,因為指令碼會認為 |468| 不存在。相反地,指令碼會認為我們的字串中有下列這些項目 (不會計入所有無關的字元):

  • |159|

  • 468

  • |572|

  • 843|

有辦法可以解決這個問題嗎?是的,但那超出我們今天所要討論的範圍。而且 RS 不需要那些東西。

剩下的就是修改我們的 For Each 迴圈。因為縱線字元是 Pattern 的一部分,這意謂縱線字元會包含在我們找到的數值中。按照預設,指令碼將會傳回下列各項:

|159
|468
|572
|843

若要排除這些縱線字元,我們將每個符合項目的 Value 屬性儲存在名稱為 strValue 的變數中,然後使用 Replace 函數來移除縱線字元的所有例項。

strValue = Replace(strValue, "|", "")

然後當我們回應 strValue 的值時,產生的結果如下:

159
468
572
843

這就更像我們要的結果了。

就像之前所說的,我們也可以找方法來分割規則運算式。然而以 RS 的資料來看,這樣應該就可以了。

啊!算了,我們接下去說吧(您知道撰寫指令碼的情形吧:一上手就停不下來了)!這裡是修訂後的指令碼,它比剛剛那一個更簡單:

Set objRegEx = CreateObject("VBScript.RegExp")

objRegEx.Global = True
objRegEx.Pattern = "\|\d{3}(?=\|)"

strSearchString = "12|3aaaa|159333|468|572|843|aaaa|456"

Set colMatches = objRegEx.Execute(strSearchString)

For Each strMatch in colMatches
strValue = strMatch.Value
strValue = Replace(strValue, "|", "")
Wscript.Echo strValue
Next

請注意在這裡使用的 Pattern,我們已經在結尾新增了這個架構:(?=\|)。這是「positive lookahead」語法:它會告訴指令碼查看接續在三位數數字後面的縱線字元。不過,它也會告訴指令碼不要將該字元包括在最終的比對值當中,因此,我們會得到類似 |468 的符合項目,而非得到 |468|。這樣會釋放縱線字元,讓它可在下一次的搜尋中使用。

就像之前說的,這樣會變得有點複雜。不過我們不會拿這些細節來煩你,至少不是今天。然而使用 positive lookahead 方法,比單單只尋找縱線字元後面接續三位數數字的指令碼更好,因為該指令碼會將 |159 視為符合項目。為什麼呢?因為它在縱線字元後面有三位數數字。當然啦!159 只是 159333 的一部分,因此它不應該包括在任何符合項目中。然而不論是好是壞,我們的規則運算式只做了我們讓它做的事:它會尋找後面接著三位數數字的縱線字元,而且它會回報找到的結果。

新增 positive lookahead 便可以避免這個問題,現在那些三位數前後都必須有縱線子元。如果沒有,就不會被視為符合的項目。假設我們的搜尋字元如下:

12|3aaaa|159333|468|572|843|aaaa|456

根據這個搜尋字串,我們將會找到下列符合項目:

468
572
843

怎麼樣,夠酷吧!

RS,希望這對您有所幫助。如果幫不上任何忙,也請告訴我們。同時,您和其他閱讀此專欄的人可能有興趣參觀一下指令碼比賽 (如果您尚未參加的話)。請記得,您無需參加每一個項目,只要加入您想要參加的項目即可,請忽略您不想要參加的項目。您也可以只參加一個項目。只要參加一個項目 (即使您沒有順利完成該項目也無妨),您就有機會獲得指令碼比賽大獎,這包括指令碼編輯器、Dr. Scripto 搖頭公仔以及 Windows Vista Ultimate 喔!給您一個提示:進階級的項目 5 會用到大量的規則運算式。這對您而言應該很容易。因為畢竟您現在已經知道關於規則運算式的所有事情了。

嗯,差不多可以這樣說啦!

無論如何,請來試一下指令碼比賽吧,我們不會介意的。當然啦!我們已經有上百支指令碼正等待測試呢!但是,有句話說:愈多愈快樂。

當然啦!說得比做得容易,畢竟您沒有上百支指令碼在等待您的測試。不過,您現在已知道指令碼測試是怎麼一回事啦:一上手就停不下來了!