【テキスト操作入門】OpenTextFileを用いたログファイルの作成・追記・読み込みモードの完全攻略
レガシーシステムの保守、あるいはWindows環境におけるタスクスケジューラと連動したバッチ処理の現場において、VBScript(Visual Basic Scripting Edition)いまだにその生命力を失っていない。モダンな言語が持て囃される現在であっても、「OS標準で動作する」「追加のランタイムを一切要求しない」という絶対的なアドバンテージを持つFSO(FileSystemObject)は、インフラエンジニアや社内システム管理者にとって最後の砦である。
本稿では、FSOの心臓部とも言える `OpenTextFile` メソッドに焦点を当て、その3つのモード(読み取り・書き込み・追記)の挙動、メモリ管理、そして実運用で絶対に避けるべき罠について、チーフアーキテクトの視点から極限まで解説する。
—
1. `OpenTextFile` の本質とオブジェクトライフサイクル
`FileSystemObject.OpenTextFile` は、テキストストリームを開くための強力なインターフェースだが、その裏側にあるCOMオブジェクトのライフサイクルを意識したことはあるだろうか。
Set objFSO = CreateObject(“Scripting.FileSystemObject”)
Set objStream = objFSO.OpenTextFile(“C:\Logs\app.log”, 2, True)
‘ … 処理 …
objStream.Close
Set objStream = Nothing
Set objFSO = Nothing
VBScriptのガベージコレクションは参照カウント方式(Reference Counting)を採用している。そのため、スクリプトの終了時に自動解放されるからといって、明示的な `.Close` と `Set … = Nothing` を怠ることは、ファイルロックの解放遅延やメモリリークを招く致命的なアンチパターンである。特に長時間のループ処理や、常駐型のタスクにおいて、ストリームを開きっぱなしにすることはシステムリソースの緩慢な自殺行為に等しい。
—
2. 3つのモードの完全攻略とパラメータの真実
`OpenTextFile` の構文は以下の通りだ。
object.OpenTextFile(filename[, iomode[, create[, format]]])
ここで最も重要なのが第2引数 `iomode` と、第3引数 `create`、そして第4引数 `format` の組み合わせである。
iomode の定数と挙動
- `ForReading` (1): 読み取り専用。ファイルが存在しない場合は実行時エラー(トラップ可能なエラー)が発生する。
- `ForWriting` (2): 書き込み専用。ファイルが存在しない場合、`create` が `True` であれば新規作成される。既存のファイルが存在する場合、その内容は容赦なく上書き消去(TRUNCATE)される。
- `ForAppending` (8): 追記モード。ファイルが存在しない場合、`create` が `True` であれば新規作成される。既存データは保持され、ポインタは常にファイルの末尾に位置する。
見落とされがちな第4引数 `format`(Tristate)
- `TristateFalse` (0): ASCII(ANSI)形式で開く(デフォルト)。
- `TristateTrue` (-1): Unicode形式で開く。
- `TristateUseDefault` (-2): システムのデフォルト設定を使用する。
【現場の知見】
ログ出力において文字コードの選定は極めて重要である。日本語Windows環境でデフォルト(TristateFalse)のまま出力するとShift-JIS(CP932)になる。もしモダンなシステム連携でUTF-8が要求される場合、標準の `OpenTextFile` ではBOM付きUTF-8での出力制御が困難になるため、後述するADODB.Streamとの使い分けが必要になる点を肝に銘じておいてほしい。
—
3. 実践:堅牢なログ出力・読み込みスクリプト
以下に、実業務でそのまま流用可能な、エラーハンドリングとリソース解放を完璧に網羅したプロダクション品質のVBScriptコードを示す。
‘ ==============================================================================
‘ Script Name: LoggerUtility.vbs
‘ Description: FSOを用いた堅牢なログの作成・追記・読み込みの模範実装
‘ ==============================================================================
Option Explicit
‘ 定数の定義(VBScriptでは組み込み定数が使えない場合があるため明示的に宣言)
Const ForReading = 1
Const ForWriting = 2
Const ForAppending = 8
Const TristateUseDefault = -2
Dim fso, logPath
Set fso = CreateObject(“Scripting.FileSystemObject”)
logPath = “C:\Logs\system_operation.log”
‘ 1. ログディレクトリの自動生成(存在しない場合の防御的プログラミング)
Call EnsureDirectoryExists(fso, logPath)
‘ 2. 【書き込み/新規作成モード】ログの初期化または上書き
Call WriteLog(fso, logPath, ForWriting, “[INFO] ログシステムが初期化されました。”)
‘ 3. 【追記モード】実際の業務処理ログの追加
Call WriteLog(fso, logPath, ForAppending, “[INFO] バッチ処理を開始します。対象件数: 1,250件”)
Call WriteLog(fso, logPath, ForAppending, “[WARN] ネットワークの応答速度がしきい値を超えています。”)
Call WriteLog(fso, logPath, ForAppending, “[INFO] バッチ処理が正常に完了しました。”)
‘ 4. 【読み取りモード】ログファイルの全行読込とコンソール出力
Call ReadLog(fso, logPath)
‘ オブジェクトの完全解放
Set fso = Nothing
WScript.Echo “すべての処理が正常終了しました。”
‘ ==============================================================================
‘ 補助関数: ディレクトリが存在しなければ作成する
‘ ==============================================================================
Sub EnsureDirectoryExists(fsoObj, filePath)
Dim parentDir
parentDir = fsoObj.GetParentFolderName(filePath)
If Not fsoObj.FolderExists(parentDir) Then
fsoObj.CreateFolder(parentDir)
End If
End Sub
‘ ==============================================================================
‘ ログ書き込みプロシージャ(書き込み・追記共通)
‘ ==============================================================================
Sub WriteLog(fsoObj, filePath, mode, message)
Dim stream, timestampedMessage
timestampedMessage = “[” & Now & “] ” & message
On Error Resume Next
‘ 第3引数を True にすることで、ファイルがない場合は自動作成する
Set stream = fsoObj.OpenTextFile(filePath, mode, True, TristateUseDefault)
If Err.Number <> 0 Then
WScript.Echo “ERROR: ログファイルを開けませんでした。Error: ” & Err.Description
Exit Sub
End If
On Error GoTo 0
‘ WriteLine は末尾に改行コード (CRLF) を付与して書き込む
stream.WriteLine timestampedMessage
‘ 厳格なリソース解放
stream.Close
Set stream = Nothing
End Sub
‘ ==============================================================================
‘ ログ読み込みプロシージャ
‘ ==============================================================================
Sub ReadLog(fsoObj, filePath)
Dim stream
If Not fsoObj.FileExists(filePath) Then
WScript.Echo “WARNING: 読み込むログファイルが存在しません。”
Exit Sub
End If
On Error Resume Next
Set stream = fsoObj.OpenTextFile(filePath, ForReading, False, TristateUseDefault)
If Err.Number <> 0 Then
WScript.Echo “ERROR: ログファイルを読み込みモードで開けませんでした。Error: ” & Err.Description
Exit Sub
End If
On Error GoTo 0
WScript.Echo “— ” & filePath & ” の内容 —”
‘ AtEndOfStream プロパティを監視しながら 1行ずつ安全に読み込む(メモリ最適化)
Do While Not stream.AtEndOfStream
WScript.Echo stream.ReadLine
Loop
WScript.Echo “———————————-”
‘ リソース解放
stream.Close
Set stream = Nothing
End Sub
—
4. チーフアーキテクトが警鐘を鳴らす「パフォーマンスの罠」
上記のサンプルコード内で使用している `ReadLine` と、対極にある `ReadAll` メソッドについて、そのパフォーマンス特性を深く理解しておかなければならない。
`ReadAll` の危険性
`ReadAll` は、テキストファイルの全内容を一度にメモリ上の文字列バッファに読み込む。
- メリット: コードが極めて簡潔になり、正規表現等を用いた一括置換・解析には向いている。
- デメリット(致命的): 対象のログファイルが数テンメガバイト、あるいはギガバイト級に肥大化した際、VBScriptの実行プロセスが大量のメモリを消費し、最悪の場合は Out of Memory(メモリ不足) エラーでスクリプトがクラッシュする。
現場の鉄則
「ログファイルや外部テキストを扱う際、ファイルサイズが将来的に膨らむことが少しでも予想されるならば、`ReadAll` は絶対に使用するな。`AtEndOfStream` と `ReadLine` を組み合わせたストリーム処理(逐次処理)を徹底せよ。」
この原則を守るだけで、レガシーバッチの安定性は劇的に向上する。
—
総括
VBScriptのFSOによるテキスト操作は、一見すると枯れ切った古くさい技術に思えるかもしれない。しかし、その背後にあるオブジェクトのライフサイクル、エラーハンドリング、そしてメモリ管理の基本原則は、C#やJava、Pythonといったモダン言語におけるI/O処理の設計思想と何ら変わらない。
「動けばいい」という妥協を捨て、極限まで洗練されたコードを書くこと――それこそが、真のインフラエンジニア、そしてシステムアーキテクトの矜持である。今日のバッチ処理から、この堅牢な実装を取り入れてほしい。
