嗨,Scripting Guy!與您的烤麵包機保持連線
Microsoft Scripting Guy
程式碼下載:HeyScriptingGuy2008_09.exe(150 KB)
如果要用幾個字總括描述我們所處的現代世界,下面四個字可以說相當貼切:保持連線。拜手機問世之賜,您再也不必待在家中苦候別人的電話,因為他們隨時隨地都能聯絡到您 (是呀,真方便哪)。而要感謝無線運算,您就算不在辦公室,也可以隨時辦公事,因為您可以在家裡、在海灘,或是在您想得到的任何地方工作。
真人真事:Scripting Editor 的爸媽最近去了一趟露營之旅,可是卻無法連上營地的無線網路,只好被迫將就度日 (就像偉大的探險家 Lewis 和 Clark 一樣),幸好衛星電視還能用,真是謝天謝地!
不僅如此,GPS 裝置還可以讓您精確知道您所在的位置 (不出幾英呎的差距),有的裝置甚至可以讓別人精確知道您所在的位置 (所謂「跑得了一時,藏不了一世」這句古語,用來形容現今的情況,真是再貼切不過了)。如果撰寫這篇專欄的 Scripting Guy 想要的話,還可以設定在每次支票過戶時讓支票帳戶打電話通知他;同樣的,也可以設定讓他的車每月以電子郵件寄送進度報告給他。不只如此,烤麵包機還可以在他外出渡假時,提供蹓狗和澆花的服務。
好啦,最後那項服務也許太誇張了 — 未來可難說了。不過如果想要的話,Scripting Guy 可以購買一部能夠連接網際網路的烤麵包機,然後在回家的路上打電話給烤麵包機,請烤麵包機準備好熱騰騰的吐司等他進門。是不是真的會有人想要在進門時看到熱騰騰的吐司,我們不知道啦,不過,想要的話…
當然嘍,如果每個人都要保持連線的話,那麼當那些從不追逐流行的 Scripting Guy 主張多多斷線時,不必太過驚訝。難道這表示 Scripting Guy 建議您丟掉手機或膝上型電腦嗎?當然不;就算 Scripting Guy 比手機或膝上型電腦還聰明,他們也不會這麼說。他們是建議您在指令碼集合中加上離線式資料錄集。不過,如果您要丟掉手機或膝上型電腦,我們也不會阻止您的啦。
注意根據 Harris Interactive 所做的調查,43% 的美國人曾在度假時,使用膝上型電腦來查看並傳送與工作有關的電子郵件。而超過 50% 的美國人在度假時,會用手機查看電子郵件和/或語音郵件。這還不包括 40% 已經超過一年沒放假的美國人在內。
不用說也知道,一定有很多人非常樂意在指令碼集合中加入離線式資料錄集,但問題是:他們根本不知道什麼是離線式資料錄集。好吧,為了讓您熟悉這個概念,我就簡單介紹一下。離線式資料錄集可以說是一個資料庫資料表,但它並沒有連到真正的資料庫,而是由指令碼建立,只存在於記憶體中,指令碼一結束,它就跟著消失。換句話說,離線式資料錄集是一個虛構的資料結構,只存在幾分鐘就跟著資料一起消失了。嘩!聽起來真的很好用耶,Scripting Guy。多謝了!
好啦,我們承認啦,離線式資料錄集聽起來沒那麼吸引人,而且也真的不吸引人,不過有時候真的相當實用。經驗老道的 VBScript 編寫人員很清楚,VBScript 的資料排序功能並非世界無敵 (除非您認為沒有一個資料排序功能稱得上世界無敵)。同樣的,VBScript 處理大量資料的功能也很有限,它不能處理 Dictionary 物件 (它會限制您使用最多具有兩個屬性的項目) 或陣列 (主要限於單屬性資料清單),嗯 … 就這兩個。
但是離線式資料錄集可以解決這兩個問題 (還有其他問題)。您需要排列資料,尤其是排列多屬性資料嗎?沒問題,就像我們說的,離線式資料錄集是虛擬的資料庫資料表,沒有什麼比排列資料庫資料表更容易的事了 (好吧,如果您還想雞蛋裡挑骨頭的話,那麼不排列資料庫資料表,當然是比排列資料庫資料表容易啦)。要不然,您需要追蹤一大組多屬性的項目嗎?沒問題,我們不是說過離線式資料錄集是虛擬的資料庫資料表嗎?您需要以某種方式篩選那些資訊,或者在那些資料中搜尋某個特定值嗎?啊,要是有辦法可以使用虛擬資料庫資料表就好了 …
說得好,也許該是示範給您看的時候了 (如果我們知道該示範什麼就好了)。好,假設 [圖 1] 是我們從 MLB.com 網站搜尋到的棒球統計資料,它們是儲存在以 Tab 鍵分隔各值的 C:\Scripts\Test.txt 中。
[圖 1] 儲存在以 Tab 鍵分隔各值的檔案中的統計資料
球員 | 全壘打數 | RBI | 平均值 |
---|---|---|---|
D Pedroia | 4 | 28 | .276 |
K Kouzmanoff | 8 | 25 | .269 |
J Francouer | 7 | 35 | .254 |
C Guzman | 5 | 20 | .299 |
F Sanchez | 2 | 25 | .238 |
I Suzuki | 3 | 15 | .287 |
J Hamilton | 17 | 67 | .329 |
I Kinsler | 7 | 35 | .309 |
M Ramirez | 12 | 39 | .295 |
A Gonzalez | 17 | 55 | .299 |
看起來很不錯。不過,如果我們要的是用全壘打數排列的球員清單呢?離線式資料錄集能夠幫我們做到這樣的事嗎?答案馬上就揭曉;請見 [圖 2]。看到一大堆程式碼了嗎?不過別擔心,等一下就知道它們其實沒這麼恐怖。
[圖 2] 離線式資料錄集
Const ForReading = 1
Const adVarChar = 200
Const MaxCharacters = 255
Const adDouble = 5
Set DataList = CreateObject("ADOR.Recordset")
DataList.Fields.Append "Player", _
adVarChar, MaxCharacters
DataList.Fields.Append "HomeRuns", adDouble
DataList.Open
Set objFSO = _
CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.OpenTextFile _
("C:\Scripts\Test.txt", ForReading)
objFile.SkipLine
Do Until objFile.AtEndOfStream
strStats = objFile.ReadLine
arrStats = Split(strStats, vbTab)
DataList.AddNew
DataList("Player") = arrStats(0)
DataList("HomeRuns") = arrStats(1)
DataList.Update
Loop
objFile.Close
DataList.MoveFirst
Do Until DataList.EOF
Wscript.Echo _
DataList.Fields.Item("Player") & _
vbTab & _
DataList.Fields.Item("HomeRuns")
DataList.MoveNext
Loop
我們先定義四個常數:
- ForReading。這個變數是用來開啟文字檔,以及從文字檔讀取。
- adVarChar。這是標準的 ADO 常數,用來建立使用 Variant 資料型別的欄位。
- MaxCharacters。這是標準的 ADO 常數,用來指出 [變數] 欄位能容納的字元數上限 (此處是 255)。
- adDouble。最後一個 ADO 常數,用來建立使用 double (數值) 資料型別的欄位。
定義常數之後,就會看到下面這個程式碼區塊:
Set DataList = CreateObject _
("ADOR.Recordset")
DataList.Fields.Append "Player", _
adVarChar, MaxCharacters
DataList.Fields.Append "HomeRuns", _
adDouble
DataList.Open
這是我們真正設定離線式資料錄集的指令碼部分。首先我們必須建立一個 ADOR.Recordset 物件的執行個體;不用說,此舉會建立一個虛擬資料庫資料表 (也就是離線式資料錄集)。
接著再使用這行程式碼 (以及 Append 方法),在資料錄集加入一個新欄位:
DataList.Fields.Append "Player", adVarChar, MaxCharacters
您瞧,一點都不複雜:只不過呼叫 Append 方法,還有三個參數而已:
- 欄位的名稱 (球員)。
- 欄位的資料型別 (adVarChar)。
- 欄位所能儲存的字元數上限 (MaxCharacters)。
加上 [球員] 欄位之後,可以再加上第二個欄位:[全壘打數],其資料型別是數值 (adDouble)。完成這個工作之後,接下來就是呼叫 Open 方法,宣告離線式資料錄集已經開啟,並且可以用於商業用途了。
接著再建立 Scripting.FileSystemObject 物件的執行個體,然後開啟檔案 C:\Scripts\Test.txt。這個部分的指令碼其實與離線式資料錄集無關;只不過因為我們需要從文字檔擷取資料,才會加入這個部分。文字檔的第一行包含標頭資訊:
Player Home Runs RBI Average
離線式資料錄集並不需要這項資訊,所以開啟檔案之後的第一件事,就是呼叫 SkipLine 方法,跳過第一行:
objFile.SkipLine
移到真正資料的第一行之後,接下來就是設定 Do Until 迴圈,這個迴圈是設計成逐行讀取剩下的部分。每當我們讀取檔案的一行時,就會把該值儲存在一個名叫 strLine 的變數中,然後用 Split 函數將這一行轉換成數值陣列 (每遇到一個定位點,就會斷開那一行):
arrStats = Split(strStats, vbTab)
沒錯,我們只是簡單帶過,但是我們假設大部分看這篇文章的人,應該都知道如何從文字檔擷取資訊。長話短說,第一次執行迴圈時,陣列 arrStats 包含 [圖 3] 所示的項目。
[圖 3] 陣列的內容
項目號碼 | 項目名稱 |
---|---|
0 | D Pedroia |
1 | 4 |
2 | 28 |
3 | .276 |
現在我們已經準備好來點樂子了:
DataList.AddNew
DataList("Player") = arrStats(0)
DataList("HomeRuns") = arrStats(1)
DataList.Update
我們要在離線式資料錄集加上 1 號球員 (D Pedroia) 的相關資訊。要在資料錄集加上一筆記錄,必須先呼叫 AddNew 方法,以建立一筆新的空記錄供我們使用。接著再使用程式碼的下兩行,將這些值指派給兩個資料錄集欄位 ([球員] 和 [全壘打數]),再呼叫 Update 方法,正式將該筆記錄寫到資料錄集當中。最後再回到迴圈開頭,針對文字檔中的下一行 (球員) 重複執行這道程序。看到沒?也許還是有一大堆程式碼,不過現在看起來整齊清爽多了。
那麼,當所有球員都加入離線式資料錄集之後會怎麼樣呢?等我們關閉文字檔之後,再來執行這個程式碼區塊:
DataList.MoveFirst
Do Until DataList.EOF
Wscript.Echo _
DataList.Fields.Item("Player") & _
vbTab & _
DataList.Fields.Item("HomeRuns")
DataList.MoveNext
Loop
我們在第 1 行使用 MoveFirst 方法,將游標定位在離線式資料錄集的開端;如果不這麼做,可能只會顯示離線式資料錄集的一部分資料。接著就是設定 Do Until 迴圈,這個迴圈會持續執行,直到用完資料為止 (也就是到達離線式資料錄集的檔案結尾為止 — 屬性為 True)。
在迴圈裡面,我們只要回應 [球員] 和 [全壘打數] 欄位的值 (請注意,用來指出特定欄位的語法有點奇特:DataList.Fields.Item("Player")。最後再呼叫 MoveNext 方法,進入資料錄集的下一筆記錄就行了。
不說也知道,相當簡單吧。等該做的都做了之後,就會得到下面這個結果:
D Pedroia 4
K Kouzmanoff 8
J Francouer 7
C Guzman 5
F Sanchez 2
I Suzuki 3
J Hamilton 17
I Kinsler 7
M Ramirez 12
A Gonzalez 17
您瞧,很不錯吧!好啦,仔細想想,其實也沒那麼棒啦。就算我們取得球員姓名和全壘打總數,還是沒有把全壘打總數排好順序。真是的,離線式資料錄集怎麼沒幫我們排好資料順序啊?
其實原因是:我們根本沒告訴資料錄集我們想要依照哪一個欄位來排序。不過這很容易改正:只要修改指令碼,先加入排序資訊,接著再呼叫 MoveFirst 方法就行了。換句話說,讓那一段程式碼看起來像這樣:
DataList.Sort = "HomeRuns"
DataList.MoveFirst
很清楚,沒什麼花招,只不過是把 [全壘打數] 欄位指派給 Sort 屬性罷了。讓我們看看執行程式碼的結果:
F Sanchez 2
I Suzuki 3
D Pedroia 4
C Guzman 5
J Francouer 7
I Kinsler 7
K Kouzmanoff 8
M Ramirez 12
J Hamilton 17
A Gonzalez 17
好多了。不過,還有一個問題:通常全壘打總數是以遞增順序列示,全壘打數最多的球員排在第一個。有沒有什麼辦法可以用遞增順序排列離線式資料錄集呢?
當然有,只要加上好用的 DESC 參數就行了,請看下面:
DataList.Sort = "HomeRuns DESC"
那 DESC 參數能幫我們做什麼呢?說得好:
A Gonzalez 17
J Hamilton 17
M Ramirez 12
K Kouzmanoff 8
I Kinsler 7
J Francouer 7
C Guzman 5
D Pedroia 4
I Suzuki 3
F Sanchez 2
順帶一提,您可以依照多個屬性排序;只要指派每一個屬性給排列順序就行了。舉個例說,假設您想要先依照全壘打數,再依照 RBI 排序。沒問題,下面這個命令就可以幫您做到:
DataList.Sort = "HomeRuns DESC, RBI DESC"
您不妨試試看,親身體驗一下。雖然沒有度假時查看電子郵件那麼好玩,不過也不會差太多啦。
注意請記住,還沒加到資料錄集的欄位,不可以用來排列順序。這是什麼意思呢?這意思是,您在加入像 RBI 這樣的屬性到 Sort 陳述式之前,必須在指令碼的適當位置中加入下列幾行:
DataList.Fields.Append "RBI", adDouble
DataList("RBI") = arrStats(2)
如果要查看結果,也必須修改 Wscript.Echo 陳述式:
Wscript.Echo _
DataList.Fields.Item("Player") & _
vbTab & _
DataList.Fields.Item("HomeRuns") & _
vbTab & DataList.Fields.Item("RBI")
讓我們看看,離線式資料錄集還能做什麼?喔,有了:假設我們擷取所有球員的所有資訊,然後依照打擊率來排序 (除了別的意思之外,這表示我們必須修改原始指令碼,建立名叫 [RBI] 和 [打擊率] 的欄位)。結果如下所示:
J Hamilton 17 67 0.329
I Kinsler 7 35 0.309
A Gonzalez 17 55 0.304
C Guzman 5 20 0.299
M Ramirez 12 39 0.295
I Suzuki 3 15 0.287
D Pedroia 4 28 0.276
K Kouzmanoff 8 25 0.269
J Francouer 7 35 0.254
F Sanchez 2 25 0.238
很好,但萬一我們只是要安打數 .300 或 .300 以上的球員名單呢?我們該如何做到只顯示符合某特定條件的球員呢?這個嘛,其中一個方法就是在資料錄集指定篩選器:
DataList.Filter = "BattingAverage >= .300"
資料錄集篩選器的功能相當於資料庫查詢:它提供一個機制,規定只傳回離線式資料錄集所有記錄的其中一部分。在這個案例中,我們只要規定篩選器只保留 [打擊率] 欄位值大於等於 .300 的記錄,其他全部去掉就行了。您猜結果如何?篩選器的表現完全符合我們的要求:
J Hamilton 17 67 0.329
I Kinsler 7 35 0.309
A Gonzalez 17 55 0.304
要是我們家小朋友也能這麼聽話就好了。
對了,您可以在一個篩選器內使用多項條件。比方說,下面這個命令就規定只傳回 [打擊率] 欄位值大於等於 .300,而 [全壘打數] 欄位值大於 10 的記錄:
DataList.Filter = _
"BattingAverage >= .300 AND HomeRuns > 10"
相較之下,這個篩選器則是規定只傳回 [打擊率] 欄位值大於等於 .300,或 [全壘打] 欄位值大於 10 的記錄:
DataList.Filter = "BattingAverage >= .300 OR HomeRuns > 10"
試試看,您就會明白哪裡不一樣。哎呀,我在鬼扯什麼:您也可以試試下面這個篩選器,就當作好玩吧:
DataList.Filter = "Player LIKE 'I*'"
結果顯示,您也可以在篩選器裡使用萬用字元。方法是使用 LIKE 運算子 (而不是等號),然後再使用星號,就像您在執行一個像 dir C:\Scripts\*.txt 的 MS-DOS® 命令時一樣。在前面的範例中,我們會得出一份姓名以 I 開頭的球員名單;那是因為我們所用的語法是:「請列出一份 [球員] 欄位值以 I 開頭,後面是什麼都無所謂的所有記錄」。不妨試 — 好啦,您想也知道我要說什麼了。
對了,您也沒有陷入像 0.309 這種打擊率的陷阱 (通常表示打擊率的數字都會省略前面的 0,例如 .309)。不過無所謂,您可以只用 FormatNumber 函數,以您要的任何古老方法設定打擊率的格式:
FormatNumber (DataList.Fields.Item("BattingAverage"), 3, 0)
顯示這個數字時,只要在 Wscript.Echo 陳述式中加入這個函數即可 (或者,也可以把輸出指派給一個變數,再將該變數置於您的 Echo 陳述式中):
Wscript.Echo _
DataList.Fields.Item("Player") & _
vbTab & _
DataList.Fields.Item("HomeRuns") & _
vbTab & DataList.Fields.Item("RBI") & _
vbTab & _
FormatNumber _
(DataList.Fields.Item("BattingAverage"), _
3, 0)
好玩吧?
不過,看來這個月已經沒時間了。簡單說就是 — 抱歉,電話響了。
不管怎樣,我們想提的是 — 喔,太好了,連手機也響了。我們剛剛收到一封從烤麵包機傳來的電子郵件。重要事項:熱騰騰的吐司已經準備好了,要奶油還是果醬呢?好啦,我們該走了,不過下個月還會再見!
Dr. Scripto 的指令碼謎題 (Scripting Perplexer)
這個每月一次的挑戰不僅測試您的解謎功力,更要測試您的指令碼技巧。
2008 年 9 月:指令碼搜尋
下面是一個簡單 (也許沒那麼簡單啦) 的字詞搜尋。請從清單中找出所有的 VBScript 函數和陳述式。但有個地方要注意:其餘的字母會拼出一個隱藏字詞,而那個字詞剛好就是 — 您猜猜看 — 一個 Windows PowerShell™ 指令程式!
字詞清單:Abs、Array、Atn、CCur、CLng、CInt、DateValue、Day、Dim、Else、Exp、Fix、InStr、IsEmpty、IsObject、Join、Len、Log、Loop、LTrim、Mid、Month、MsgBox、Now、Oct、Replace、Set、Sin、Space、Split、Sqr、StrComp、String、Timer、TimeValue、WeekdayName。
答案是:
Dr. Scripto 的指令碼謎題 (Scripting Perplexer)
答案是:2008 年 9 月:指令碼搜尋
The Scripting Guy 為 Microsoft 做事,也就是受雇於 Microsoft。他們在不玩、不教或不看棒球 (以及其他各種活動) 的時候,就負責管理 TechNet 指令碼中心。請造訪他們的網站:www.scriptingguys.com。