嗨,Scripting Guy!

嗨,Scripting Guy!

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

還有,別忘了瞧瞧全新經過改良的嗨,Scripting Guy!過往文件

今天的問題:如何取得一組文字檔案中的最後一行,並將該項資訊貼到 Word 文件中?


如何將一組文字檔案的最後一行加入 Word 文件中?

嗨,Scripting Guy!如何取得一組文字檔案中的最後一行,並將該項資訊貼到 Word 文件中?

-- JV

JV,您好。您知道,我們在這個專欄中一直想把重點放在實際的系統管理工作。雖然偶爾會脫離常軌,不過在過去我們大都試著解決我們覺得很多人會感興趣的問題。老實說,當我們第一次看到這個問題時,覺得應該是乏人問津。

後來我們開始想 (我們有時候也是會用到腦袋,大概跟哈雷彗星出現的頻率差不多)。我們發覺到有很多記錄檔都包含一大堆詳細資訊,而最後一行就像是總結。假使您有一大堆記錄檔而且想要彙總這類的摘要報告,方法之一可能是抓取每個記錄檔的最後一行,然後將所有這些摘要行丟入 Word 文件中。我們不清楚這是不是您所面臨情況,不過我們有實際想到實用的用途就很興奮,於是決定來回答這個問題。

我們的解答如下:

Const ForReading = 1
Set objFSO = CreateObject("Scripting.FileSystemObject")
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colFileList = objWMIService.ExecQuery _
    ("ASSOCIATORS OF {Win32_Directory.Name='C:\Logs'} Where " _
        & "ResultClass = CIM_DataFile")
For Each objFile In colFileList
    strFilePath = objFile.Name
    Set objTextFile = objFSO.OpenTextFile(strFilePath, ForReading)
    Do Until objTextFile.AtEndOfStream
        strLine = objTextFile.ReadLine
    Loop
    strMessage = strMessage & strLine & vbCrLf
    objTextFile.Close
Next
Set objWord = CreateObject("Word.Application")
objWord.Visible = True
Set objDoc = objWord.Documents.Add()
Set objSelection = objWord.Selection
objSelection.TypeText strMessage

好啦,是有點長,不過記住這裡要執行的工作不只一項:取得資料夾中的全部檔案,將之開啟,抓取最後一行,啟動 Microsoft Word,然後將所有這些行貼到 Word 文件中。要在 20 幾行的程式碼裡完成這些工作還不算太差。

指令碼開始是先定義一個叫做 ForReading 的常數,之後為此常數指派 1 的值,我們每次開啟和讀取文字檔時都會用到 ForReading。接下來我們連接到本機電腦上的 WMI 服務。現在問題來了:這個指令碼一定要在本機電腦上執行嗎?答案是:不一定,畢竟 WMI 也可以從遠端電腦抓取檔案集合,就跟從本機電腦上抓取一樣簡單。

不過,可惜的是,FileSystemObject (我們用來讀取文字檔的物件) 處理遠端機器起來就沒那麼順手了。要是我們在開啟檔案時,參考的是系統管理共用的話,還是可以用它來進行。比方說,要開啟遠端電腦 atl-fs-01 上位於 C:\Logs 中的 Log1.txt 檔案,得使用像這樣的路徑:

\\atl-fs-01\C$\Logs\Log1.txt

這雖可行,可是您得加入一些程式碼來建構類似如下的路徑:

 

附註:好啦,算您厲害!我們在今天的專欄不能這麼做,可是在接下來幾天,我們會找機會向您說明如何新增這類的程式碼到這個指令碼中。


現在讓我們來看看,嗯,我們剛剛說到哪了?喔,對!在連接到 WMI 服務之後,我們接著使用此項查詢來擷取 C:\Logs 資料夾中所有檔案的清單:

Set colFileList = objWMIService.ExecQuery _
    ("ASSOCIATORS OF {Win32_Directory.Name='C:\Logs'} Where " _
        & "ResultClass = CIM_DataFile")

要另外註明的是,我們假設您是想要讀取資料夾中的全部檔案。如果不是的話,那麼就需要另外加入一兩行程式碼,以篩選出您不想要的檔案 (例如,您可能要檢查一下副檔名,並且只讀取具有 .log 的副檔名)。

跟您預期的一樣,WMI 查詢會傳回 C:\Logs 資料夾中所有檔案的集合,我們接著建立 For Each 迴圈來跑過該項集合。我們會對集合中的每個檔案使用這行程式碼來擷取檔名 (也就是相等於檔案路徑的 WMI):

strFilePath = objFile.Name

接下來我們呼叫 OpenTextFile 方法來開啟要讀取的檔案。接著是這小段程式碼:

Do Until objTextFile.AtEndOfStream
    strLine = objTextFile.ReadLine
Loop

這裡的動作是要做什麼呢?話說 FileSystemObject 並不是什麼聰明的文字解析器,例如,它只能從上到下來讀取檔案,所以不可能叫它「只要讀這個檔案的最後一行」。因此,我們必須一行一行讀取檔案。每次讀取一行,就會將該行的內容存放到一個叫做 strLine 的變數 (取代之前在該變數中的內容)。當讀取到檔案的最後一行時 (也就是我們要的那一行),該行 (而且只有該行) 的內容就會存放到 strLine 中。

 

附註:沒錯,聽起來有點雜,也的確是蠻混雜的。不過還好,雖然 FileSystemObject 不怎麼聰明,它動作倒是很快:實際上說來,它一行一行讀取檔案的速度,就跟跳到檔案的最後只讀取最後一行一樣快。


現在我們在 strLine 變數堆存了檔案的最後一行,接下來呢?不妨看看這行程式碼:

strMessage = strMessage & strLine & vbCrLf

我們在這裡取得最後一行,並將它加入一個叫做 strMessage 的變數。注意上面的等式:我們是將剛好在 strMessage 中的值指派給 strMessage,加上 strLine 的值,再加上復位換行組 (vbCrLf)。換句話說,假設我們的第一檔案最後一行的內容是:

File 1

第一次跑完迴圈,strMessage 會有相同的值。現在,假設第二個檔案的最後一行是 File 2。那麼第二次跑完迴圈,strMessage 會等於:

File 1
File 2

如您所見,我們每跑一次,就在記憶體中建構一份所有最後一行的清單。

在將 strLine 的內容加入 strMessage 變數之後,我們隨後關閉第一個檔案,繼續迴圈處理集合中的下一個檔案重複相同的程序。

一旦讀取所有的檔案之後,就該啟動 Word 了。這三行程式碼會啟動 Word.Application 物件的執行個體,使該執行個體顯示在螢幕上,然後給我們一份全新空白的文件:

Set objWord = CreateObject("Word.Application")
objWord.Visible = True
Set objDoc = objWord.Documents.Add()

現在就只剩下建立 Word Selection 物件的執行個體,接著使用 TypeText 方法將我們的文字鍵入文件中:

Set objSelection = objWord.Selection
objSelection.TypeText strMessage

那麼我們要鍵入的文字是什麼?沒錯,strMessage 的值,正是包含每個文字檔最後一行的變數。也就是說,在技術上,我們並不是傳遞該資訊到 Word 中,但是最後結果同出一徹。想想這還都是我們自己想出來的呢…

附註:我們在測試這個指令碼的時候碰到一個檔案,在放到 Word 時,會在行裡的每個字元之間留下非列印字元。老實說,我們沒什麼時間更深入詳細調查,不過,如果您碰到此問題,這個修訂版可充分修正問題:它使用 Word 的 CleanString 方法來移除字串中的非列印字元。您可能會發現間距有點不太對稱,可是得到的結果行還是比較容易閱讀。

這裡是修訂過的指令碼,只多了一行程式碼,也就是呼叫 CleanString 方法的那行:

Const ForReading = 1

Set objFSO = CreateObject("Scripting.FileSystemObject")

strComputer = "."

Set objWMIService = GetObject("winmgmts:\" & strComputer & "\root\cimv2")

Set colFileList = objWMIService.ExecQuery _ ("ASSOCIATORS OF {Win32_Directory.Name='C:\Logs'} Where " _ & "ResultClass = CIM_DataFile")

For Each objFile In colFileList strFilePath = objFile.Name Set objTextFile = objFSO.OpenTextFile(strFilePath, ForReading) Do Until objTextFile.AtEndOfStream strLine = objTextFile.ReadLine Loop strMessage = strMessage & strLine & vbCrLf objTextFile.Close Next

Set objWord = CreateObject("Word.Application") objWord.Visible = True Set objDoc = objWord.Documents.Add() strMessage = objWord.CleanString(strMessage)

Set objSelection = objWord.Selection objSelection.TypeText strMessage


如需詳細資訊

查看嗨,Scripting Guy!- 過往文件

 

回到頁首 回到頁首