Exec()を使うときは標準出力を先に読み込むようにする

仕事でコマンドからデータを受け取ったり加工したりしなければならなかった。ちょっとDOSスクリプトじゃ荷が重い作業でVBScriptを使ったのだけれど,VBScriptをよく知らないせいかはまってしまった。Exec() で実行した外部コマンドの標準出力を受け取って加工するだけなのだけれど,途中でプログラムが止まってしまうのである。プログラムの概要は次みたいな感じ。

Main.vbs

Option Explicit

Dim objExec

' カレントディレクトリ下のディレクトリ構造を取得する
Set objExec = CreateObject("WScript.Shell").Exec("cmd /C tree /F")

' プログラムが終了するまで待つ (終了すると Status の値が 1 になる)
Do While objExec.Status = 0
	WScript.Sleep 100
Loop

' エラーが発生していないことを確認する (正常終了時の ExitCode は 0)
If objExec.ExitCode <> 0 Then
	WScript.Echo "エラーが発生しました"
	WScript.Quit
End If

' ディレクトリ構造を tree コマンドの標準出力から読み取って書き出す。
WScript.Echo objExec.StdOut.ReadAll

実行例

C:\>cscript C:\tmp\main.vbs
Microsoft (R) Windows Script Host Version 5.7
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.


※ いつまでたってもこの状態から進まない

どうも,標準出力へデータを大量に書き出すコマンドをExec()するとまずいらしい。事実,ファイルが1つも存在しないディレクトリ上でMain.vbsを実行すると正常に動作する。振る舞いから察するにパイプがいっぱいになって読み込み待ちが発生しているみたいなので,コマンドが標準出力に書き出したデータを読み込んでからエラーチェックしてみるとうまく動いた。
適当にプログラムを書いてパイプの詰まり具合を調べてみると,どうもバッファのサイズは4096バイトらしい。つまり,標準出力へ4096バイト以上書き出すプログラムをExec()する場合は定期的にパイプからデータを読み出すなどしてバッファが詰まらないようにする必要がある。また,標準エラー出力のバッファも4096バイトらしく標準出力の場合と同じ問題が発生する。
まー,実際のところ4096バイトくらいバッファがあれば(ウルトラ親切なエラーメッセージを表示するプログラムで無い限り)標準エラー出力が詰まることはないのだろうけど,標準出力は問題だよなー。パイプを使ったプログラムはC言語でもPerlでも書いたことあるけれど,こんなところで詰まったのは初めて。今までは単に運がよかっただけなんかなぁ。