UWSCで標準入出力制御

UWSC公式掲示板で教えて頂いた方法をモジュール化。
感謝です!




なお、本方法では、上手くいかないケースがある。
その場合は、別な方法で。
UWSCでコマンドプロンプト - じゅんじゅんのきまぐれ


Stdinout.uws

OPTION EXPLICIT

IFB GET_UWSC_NAME="Stdinout.uws" THEN
	WITH Stdinout
		//.Exec("cmd")
		//.Write("for /l %i in (1,1,5) do @ping -n 2 www.yahoo.co.jp<#CR>")
		.Exec("cmd /c for /l %i in (1,1,5) do @ping -n 2 www.yahoo.co.jp<#CR>", "cmd.exe")
		DIM text, loop = TRUE
		WHILE loop
			SLEEP(3)
			text = .Read()
			IFB LENGTH(text) THEN
				PRINT text
			ELSE
				loop = FALSE
			ENDIF
		WEND
		//.Write("exit<#CR>")	// 「for〜」Write版では、これが必要。.Kill()という手もあるけど
		//.Kill()	// .Kill()で呼んでるメソッドは最終手段だそうな
		.Close()
	ENDWITH
ENDIF



MODULE Stdinout
	DIM _wshShell
	DIM _exec = -1
	DIM _wait = FALSE
	DIM _getNewText = ""

	PROCEDURE Stdinout()
		_wshShell = CREATEOLEOBJ("WScript.Shell")
	FEND

	// Execの多重呼び出しには対応してません
	// 多重呼び出ししたいなら、Module内部変数を配列か何かにしないと
	FUNCTION Exec(cmd, title=EMPTY)
		Close()
		_exec = _wshShell.Exec(cmd)
		THREAD PrivateReadThread()
		IF title <> EMPTY THEN CTRLWIN(GETID(title), HIDE)
		RESULT = _exec
	FEND

	PROCEDURE PrivateReadThread()
		WHILE !_exec.StdOut.AtEndOfStream
			DIM read = _exec.StdOut.Read(1)
			// いい加減な排他処理。まいいでしょう
			WHILE _wait; SLEEP(0.001); WEND
			_wait = TRUE
			_getNewText = _getNewText + read
			_wait = FALSE
		WEND
	FEND

	FUNCTION Read()
		// いい加減な排他処理。まいいでしょう
		// THREADで多重Read()する予定があるなら、これはNG
		WHILE _wait; SLEEP(0.001); WEND
		_wait = TRUE
		RESULT = _getNewText
		_getNewText = ""
		_wait = FALSE
	FEND

	FUNCTION Write(cmd)
		RESULT = FALSE
		IFB _exec <> -1 THEN
			_exec.Stdin.Write(cmd)
			RESULT = TRUE
		ENDIF
	FEND

	PROCEDURE Kill()
		IF _exec <> -1 THEN _exec.Terminate()
		Close()
	FEND

	PROCEDURE Close()
		_exec = -1
		_wait = FALSE
		_getNewText = ""
	FEND

ENDMODULE


ちなみに、遭遇した上手くいかないケースは、

  • バッチスクリプトからexeを呼び出している
  • それが標準入出力を使う
  • exeが標準出力し、入力待ちでは、何の文字も取得できない
  • 入力して制御が戻ると、標準出力からメッセージが取得できる(入力待ち前の文字も含めて)

Windowsバージョン等でも動作は違うんだろうなぁ。