start コマンドを使い、非同期でプログラムを実行するバッチを作成し、そのバッチの出力をパイプすると、実行したプログラムが終了するまでパイプの処理が返らない。
pause.cmd
@echo pause
@exit /b
process.cmd
@echo off
Echo 処理1開始
Rem 処理1
Echo 処理2開始
start "" cmd /s /c "pause.cmd"
exit /b
と作成し、コマンドプロンプトで単に
process.cmd
とすれば、pause.cmd を閉じなくても次の処理に進めるのだが、
process.cmd | sort
などとパイプしたとたん、処理が返らなくなってしまう。
これの対処法はいくつかある。
そもそもの原因は↓これっぽい。
Windows Script Programming : 標準出力と標準エラーを非同期にMessageBoxに出す。
http://scripting.cocolog-nifty.com/blog/2007/09/messagebox_5566.html
どうもパイプの前のプロセスにパイプのハンドルの複製が残っているのでは?
なので、プロセスを終了しないとパイプのEOFが上がらない。。。そこで、別にプロセスを起こして自分は終了するとよいようです。
ProcessStartInfo.UseShellExecute=trueでプロセスを起こすと、
ファイルハンドルを引き継がないようです。
…ということなので、OSのシェルを使って起動すればいいようだ。
(1)ショートカットから起動
たぶん一番手っ取り早い。
pause.cmd のショートカットを作成し、 pause.lnk を start させる。
start "" cmd /s /c "pause.lnk"
とするだけ。
(2)url.dll の FileProtocolHandler を使う
非同期で起動するプログラムに引数を渡さなくてよいならこれ。
rundll32.exe url.dll,FileProtocolHandler "pause.cmd"
(3)PowerShell を使う
PowerShell があるのなら若干無理矢理だが、System.Diagnostics.Process クラスを使って非同期に開始する方法も。
powershell -Command "&{$s='';for($i=1;$i -lt $args.Count;$i++){$s+=' '+[char]34+$args[$i]+[char]34};[void][Diagnostics.Process]::Start($args[0],$s)}" "pause.cmd" "arg1" "arg2"
引数のシリアライズは、もうちょっとマシな方法がある気がするが…(引用符入り引数に対応していない)
てか、もっとキレイに書け気がする。
引数がいらないならもうちょっと短くできて、
powershell -Command "&{[void][Diagnostics.Process]::Start($args[0])}" "pause.cmd"
PowerShell が使えるのなら、そもそもパイプ自体を PowerShell でやるって手もあるけど。