連載もいよいよ最終回。テキストファイルの読み書きを行うTextStreamと、連想配列やハッシュをサポートするDictionaryオブジェクトについて解説。
第16回から3回にわたって、FileSystemObjectオブジェクトの解説をしてきた。最終回となる今回は、FileSystemObjectオブジェクトを含んでいる、Microsoft Scripting Runtimeに含まれるTextStreamオブジェクトおよびDictionaryオブジェクトを取り上げ、テキスト・ファイルの読み書きの方法および辞書(連想配列、ハッシュ)の使い方について解説する。
■連載目次
第1回 WSHを始めよう
第2回 VBScript基本(1)文字列の入出力
第3回 VBScript基本(2)計算と分岐処理
第4回 関数を使いこなす:文字列、数値、日付
第5回 データ型について理解を深めよう
第6回 VBScriptの配列を極める
第7回 Subプロシージャで処理を定義
第8回 Functionプロシージャで関数を定義
第9回 VBScriptのオブジェクトを使いこなす
第10回 WScriptオブジェクト(1)
第11回 WScriptオブジェクト(2)
第12回 WshShellオブジェクト(1)
第13回 WshShellオブジェクト(2)
第14回 WshShellオブジェクト(3)
第15回 WshNetworkオブジェクト
第16回 FileSystemObjectオブジェクト(1)
第17回 FileSystemObjectオブジェクト(2)
第18回 FileSystemObjectオブジェクト(3)
第19回 TextStream/Dictionaryオブジェクト
WSHでテキスト・ファイルを扱うには、まずCreateObject関数もしくはメソッドでFileSystemObjectオブジェクトを生成し、これに含まれるOpenTextFileメソッドを呼び出し、TextStreamオブジェクトを取得し、利用するのが基本となる。TextStreamオブジェクトを使用すると、テキスト・ファイルを読み出して変数に代入することも、逆に変数に含まれる文字列などをテキスト・ファイルに書き込むことも可能である。
まずはテキスト・ファイルの読み出しから解説する。例えば、メモ帳などで、何か文章を書き(日本語の文字や改行が含まれていてもよい)、デスクトップにtest.txtというファイル名で保存してもらいたい。ここではサンプルとして次のようなテキストを用意しておく。注意:文字コードはANSI(=ASCII。日本語文字を用いるとShift_JISになる)で保存すること。
※テキスト・ファイルの例:test.txt(デスクトップ上にtest.txtというファイル名で保存しておくこと。文字コードはANSIかShift_JIS)
学生のころ、夏休みがずっと続けばいいのにと思っていた。
宿題を7月中に終わらせてあとは遊ぶのがよいのだろう。
でも夏休みを一万五千回以上も繰り返すのは嫌だ。
このテキスト・ファイルの内容をメッセージ・ボックスに表示させるには、次のようなコードを記述すればよい。
※ファイル:ReadTextFile.vbs
Option Explicit
Dim objFSO, objWshShell
Set objFSO = WScript.CreateObject("Scripting.FileSystemObject")
Set objWshShell = WScript.CreateObject("WScript.Shell")
'デスクトップ・フォルダの取得とtest.txtのフルパスの構築
Dim strFilePath
strFilePath = objFSO.BuildPath( _
objWshShell. SpecialFolders("Desktop"), _
"test.txt")
Dim objTextStream
Dim strLines
On Error Resume Next
'テキスト・ファイルを開き、TextStreamオブジェクトを取得
Set objTextStream = objFSO.OpenTextFile(strFilePath)
'TextStreamが最後尾の位置になるまで繰り返す
Do Until objTextStream.AtEndOfStream
'1行を読み出し、文字列変数に追記する
strLines = strLines & objTextStream.ReadLine() & vbCrLf
Loop
objTextStream.Close() 'TextStreamを閉じる
Set objTextStream = Nothing
On Error Goto 0
MsgBox strLines 'テキストの中身をメッセージ・ボックスに表示
Set objWshShell = Nothing
このスクリプトを実行するとファイルの内容が読み出され、次のように表示される。
このスクリプトではまずWshShellオブジェクトを用いてデスクトップ・フォルダの位置を確認しているが、これについては第14回を参照してもらいたい。
次に、FileSystemObjectオブジェクトのOpenTextFileメソッドに、テキスト・ファイルのフルパスを引数として渡し、テキスト・ファイルを開いている。このとき、TextStreamオブジェクトが返され、開いたテキスト・ファイルにはロックがかかる。このTextStreamオブジェクトを先頭から終わりまで順に読み出し(シーケンシャルにReadし)、テキスト・ファイルの中身の文字列を変数に代入する。ここではReadLineメソッドを用いて、TextStreamを1行ずつ読み出している。ReadLineメソッドを実行すると、テキスト・ファイルの1行が文字列として返され、同時に現在読み出しているTextStreamの位置(Lineプロパティで取得可能)を1増やす。これをTextStreamの末端、すなわち最終行までDoループで繰り返している。TextStreamの現在の位置が、TextStreamの末端であると、AtEndOfStreamプロパティがTrueを返すので、これをループの終了条件としている。
TextStreamを最後まで読み出してもファイルのロックは解除されないので、最後にCloseメソッドを呼び出し、ファイルを解放する。これでほかのプロセスから再びtest.txtにアクセス可能になる。
あとは、返された文字列をメッセージ・ボックスに表示しているだけである。
ここでは、OpenTextFile メソッドにファイルパス以外の引数を指定していないので、読み出し専用で開かれているが、ほかの引数を指定することで、書き込み専用で開くこともできる。それについては後で述べる。読み出し専用で開いていることを明示したければ、OpenTextFileメソッドの第2引数に1という値を指定(定数ForReadingを割り当てると分かりやすい)し、
Const ForReading = 1, ForWriting = 2, ForAppending = 8
……(中略)……
Set objTextStream = objFSO.OpenTextFile(strFilePath, ForReading)
とするとよいだろう。また、OpenTextFile メソッドは文字コードを指定してテキスト・ファイルを開くこともできる。対応しているのはASCII(Shift_JIS)とUnicode(UTF-16)である。Unicodeで開く場合は以下のように第4引数をTrueにする。
Set objTextStream = objFSO.OpenTextFile(strFilePath, , , True)
残念ながらUTF-8には対応していない。省略時(Falseのとき)はASCII(Shift_JIS)と見なして開く。
注:WSHからUTF-8のファイルを読み書きするには、ADODB.Streamオブジェクトを使えば可能である。ADODB.Streamオブジェクトの使用方法については「ADO Stream オブジェクト(マイクロソフトMSDNサイト)」を参考にしていただきたい。
TextStreamを読み出すには、1行ずつ読み出すReadLineメソッドのほか、1文字あるいは数文字ずつ読み出すReadメソッドが存在する。引数に読み出す文字数を指定する。その際、現在の位置はColumnプロパティで参照できる。現在の読み出す位置が行末かどうかはAtEndOfLineプロパティで判定できる。また、TextStream全体を読み出すReadAllメソッドも存在する。先ほどの例ではReadLineメソッドを用いて1行ずつ読み出していたが、行ごとに何か処理を行うということはしていないので、そういう場合はReadAllメソッドを使ってもよい。その場合、
strLines = objTextStream.ReadAll()
として一気に文字列を変数に代入できる。
行の読み出しを1行飛ばす場合はSkipLineメソッドを用いる。同様にReadメソッドに対応する読み出しを数文字飛ばすSkipメソッドも存在する(引数に飛ばす文字数を指定する)。これらのメソッドは行または文字をスキップするのでその部分は読み出されない。
テキスト・ファイルのTextStreamを開くには、上の例のようにFileSystemObjectオブジェクトのOpenTextFileメソッドを用いるほか、テキスト・ファイルを示すFileオブジェクトのOpenAsTextStreamメソッドを用いることも可能である。その場合、引数にパスを指定する必要はなく、第1引数に読み出しモードの指定、第2引数にANSIかUnicodeかを指定できる(省略可)。両者が返すTextStreamは同じものである。Fileオブジェクトを使用して、かつその内容を扱う際は後者を用いるとよい。
また、WSHでは標準入出力を扱えるが(第11回および第14回で解説済み)、それもTextStreamとして扱うので、今回紹介しているテクニックがそのまま使える。
次に、ファイルの書き込みについて述べる。先ほど用意したtest.txtに、WSHから次のような文字列を追記(末端に文字列を追加)することを考える。
とはいっても宿題は8月31日になっても手つかずで、
あとはどうとでもなれという気分になるのだろう。
これをスクリプトにすると次のようなコードになる。
※ファイル:AppendTextFile.vbs
Option Explicit
Dim objFSO, objWshShell
Set objFSO = WScript.CreateObject("Scripting.FileSystemObject")
Set objWshShell = WScript.CreateObject("WScript.Shell")
Const ForReading = 1, ForWriting = 2, ForAppending = 8
'デスクトップ・フォルダの取得とtest.txtのフルパスの構築
Dim strFilePath
strFilePath = objFSO.BuildPath( _
objWshShell. SpecialFolders("Desktop"), _
"test.txt")
Dim objTextStream
Dim strLines
On Error Resume Next
'テキスト・ファイルを開き、TextStreamオブジェクトの取得 Set objTextStream = objFSO.OpenTextFile(strFilePath, ForAppending)
'文字列行を追加
objTextStream.WriteLine "とはいっても宿題は8月31日になっても手つかずで、"
objTextStream.WriteLine "あとはどうとでもなれという気分になるのだろう。"
objTextStream.Close() 'TextStreamを閉じる
Set objTextStream = Nothing
On Error Goto 0
Set objFSO = Nothing
Set objWshShell = Nothing
このスクリプトを実行すると、test.txtに2行が追加され、メモ帳で開くと次のようになっているはずである。
このスクリプトにおいては、OpenTextFileメソッドを用いる際に、第2引数に追記モードを意味するForAppending(Constで定数宣言しているので8と等しい)を指定して、TextStreamを追記モードで開いている(注:ForWritingを指定すると、既存のファイルの内容がすべて削除され、新たにファイルの先頭から文字列が書き込まれる)。このTextStreamにWriteLineメソッドを用いて1行ずつ文字列を追記している。WriteLineメソッドを用いると、TextStreamに引数の文字列と改行コードを書き込まれる。
行ではなく文字列を書き込むにはWriteメソッドを用いる。Writeメソッドも、引数の文字列をTextStreamに書き込むのは同様であるが、末端に改行コードは付加しない。Writeメソッドを使う場合、上のスクリプトのWriteLineメソッドを呼び出しているところは次のように書くことができる。
objTextStream.Write _
"とはいっても宿題は8月31日になっても手つかずで、" & vbCrLf & _
"あとはどうとでもなれという気分になるのだろう。" & vbCrLf
また、空行を複数行書き込むためのWriteBlankLinesメソッドも用意されている(引数には挿入する空行数を指定)。
また、存在しないテキスト・ファイルを一から作成するには、OpenTextFileの第2引数にForWritingを、ファイルが存在しないときにファイルを作成するかどうかを決める第3引数にTrueを指定することで可能である(ちなみに第4引数にTrueを指定するとUnicodeで書き込める)。しかしより簡便に行うには、FileSystemObjectオブジェクトのCreateTextFileメソッドを使うとよい。
OpenTextFileと同様、引数に渡す値としてテキスト・ファイルのパスが必須となる。そのほかに、第2引数にFalseを指定すると、ファイルがすでにある場合、エラーが発生する。デフォルトはTrueで、この場合、強制的に上書きして新しいファイルを作成する。第3引数にTrueを指定するとUnicodeで書き込みが可能だ。
CreateTextFileメソッドもOpenTextFileメソッドと同様にTextStreamオブジェクトを返すので、あとは同様にWriteメソッドなどを用いて文字列を書き込むことができる。
ところで、第1回の一番最初にこんな問題を出したことを覚えている方はおられるだろうか?
「My Documentsフォルダ内にある2005年に作成されたファイルのうち、最近1カ月更新してないテキスト・ファイルの中身を確認したいので、それらのファイルの1行目を抜き出して1つのテキスト・ファイルにする
これが今回までの知識でようやく解けるようになる。そこで、練習としてこの問題を解いてもらいたい。ただしスクリプトを実際に実行するにはテスト用のファイルを用意しないといけない場合があるので、お題を少し変更して、
「My Documentsフォルダ内にある最近1カ月以内に更新したテキスト・ファイルの中身を確認したいので、それらのファイルの1行目を抜き出して1つのテキスト・ファイルにする」
としよう。
マーカーで隠れたところを選択してチェックしてみよう。
※ファイル:PickupNewFileContents.vbs
Option Explicit
Dim objWshShell, objFSO
Set objWshShell = WScript.CreateObject("WScript.Shell")
Set objFSO = WScript.CreateObject("Scripting.FileSystemObject")
Dim strMyDocumentsPath
'検索するフォルダ(マイ ドキュメント)
strMyDocumentsPath = objWshShell.SpecialFolders("MyDocuments")
Dim objTSDistination
'出力ファイルを作成する
Set objTSDistination = objFSO.CreateTextFile("out.txt")
If objFSO.FolderExists(strMyDocumentsPath) Then
'フォルダを検索する
Call SearchFolder(objFSO.GetFolder(strMyDocumentsPath))
End If
objTSDistination.Close() 'TextStreamを閉じる
MsgBox "終了しました"
Sub SearchFolder(objFolder)
'フォルダを検索するSubプロシージャ(引数はFolderオブジェクト)
Dim objFile, objSubFolder, objTSSource, strLine
For Each objFile In objFolder.Files
'フォルダに含まれるすべてのファイルに対して実行
If DateDiff("d", objFile.DateLastModified, Now) <= 31 And _
LCase(objFSO.GetExtensionName(objFile.Name)) = "txt" Then
'最終更新日が現在より1カ月=31日以内かつ拡張子がtxtならば
'TextStreamを開く
Set objTSSource = objFile.OpenAsTextStream()
If Not objTSSource.AtEndOfStream Then
strLine = objTSSource.ReadLine() '1行読む
Else
strLine = ""
End If
objTSSource.Close() 'TextStreamを閉じる
'ファイル名と、ファイルの最初の1行を出力する
objTSDistination.WriteLine "[" & objFile.Path & "]"
") objTSDistination.WriteLine strLine
End If
Next
For Each objSubFolder In objFolder.SubFolders
'サブフォルダを再帰的に検索する
Call SearchFolder(objSubFolder)
Next
End Sub
このスクリプトを実行すると、スクリプトが存在するフォルダにout.txtというファイルが生成され、そこに「マイ ドキュメント」フォルダ内にある拡張子txtのファイルのうち、1カ月以内に更新されたファイルのパスとその1行目のテキストが出力される。
例えば次のようなファイルができているだろう。
このスクリプトでは、まずスクリプトが存在するフォルダにout.txtというファイルを作成し、そのTextStreamを開く。次に「マイ ドキュメント」フォルダの位置をSpecialFoldersメソッドで確認し、対応するFolderオブジェクトをSearchFolderプロシージャに渡している。プロシージャ内ではファイルを列挙し、各ファイルについて最終更新日(DateLastModifiedプロパティ)や拡張子(NameプロパティをGetExtensionNameメソッドで調べる)を参照し、該当するFileオブジェクトに対してOpenAsTextStreamメソッドを呼び出し、TextStreamを開く。そしてReadLineメソッドで1行だけ読み出し、最初に作成したTextStreamにその文字列をWriteLineメソッドで書き込んでいる。あとはサブフォルダについても再帰呼び出しで実行している。
以上のように、TextStreamを使いこなせば、テキスト・ファイルが自由自在に扱えることがお分かりいただけたと思う。
Copyright© Digital Advantage Corp. All Rights Reserved.