Using a custom Tee command for .bat file
Solution 1:
Your attempt to call a batch function within a pipe will always fail because of how Windows pipes work - Windows instantiates both sides of the pipe via new CMD shells. See https://stackoverflow.com/a/8194279/1012053 for more info.
That Rob van der Woude version of a batch tee cannot possibly work for you because it uses a FOR /F
to read the results of a command - the command must execute to completion before any lines are read. That won't work if you need user interaction during the execution of the command. With that version of tee you might as well simply redirect output to a file and then TYPE
the file when finished. Obviously not what you want.
There are pure batch tricks that can get you closer, but I think there is still one problem that can't be solved with pure batch. Your executable may put a prompt on a line without issuing a new line. I believe pure native batch always reads entire lines (except when at end of stream). I'm not aware of a batch method to read character by character.
Slight correction - SET /P
can read partial lines of piped input, but it has limitations that prevent it from being used for a robust batch tee solution: There is no way to know for sure when each line ends. It is limited to 1021 characters per "line". It strips control characters from the end of each "line". There is no way to tell when it has reached the end of the input stream.
But there is a simple solution - JScript or VBScript works like a champ, and doesn't require any special installs. Here is a hybrid JScript/batch script that should work for you. The JScript is poorly written with lots of room for improvement. For example, there is no error checking.
I save the script as tee.bat
. The first required argument specifies the name of the file to write to. By default, the file is over-written if it already exists. If a second argument is provided (value doesn't matter), then the output is appended to the file instead.
@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment
::--- Batch section within JScript comment that calls the internal JScript ----
@echo off
cscript //E:JScript //nologo "%~f0" %*
exit /b
----- End of JScript comment, beginning of normal JScript ------------------*/
var fso = new ActiveXObject("Scripting.FileSystemObject");
var mode=2;
if (WScript.Arguments.Count()==2) {mode=8;}
var out = fso.OpenTextFile(WScript.Arguments(0),mode,true);
var chr;
while( !WScript.StdIn.AtEndOfStream ) {
chr=WScript.StdIn.Read(1);
WScript.StdOut.Write(chr);
out.Write(chr);
}
Usage is pretty much like you would expect.
command.exe | tee.bat output.txt 1
The last 1 argument forces append mode. It could be any value besides 1
It is possible to put everything in one batch script as you seem to prefer.
@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment
::--- Batch section within JScript comment ----------------------------
@echo off
::This block of code handles the TEE by calling the internal JScript code
if "%~1"=="_TEE_" (
cscript //E:JScript //nologo "%~f0" %2 %3
exit /b
)
::The rest of your batch script goes here
::This pipes to TEE in append mode
mycommand.exe | "%~f0" _TEE_ output.txt 1
exit /b
----- End of JScript comment, beginning of normal JScript ------------------*/
var fso = new ActiveXObject("Scripting.FileSystemObject");
var mode=2;
if (WScript.Arguments.Count()==2) {mode=8;}
var out = fso.OpenTextFile(WScript.Arguments(0),mode,true);
var chr;
while( !WScript.StdIn.AtEndOfStream ) {
chr=WScript.StdIn.Read(1);
WScript.StdOut.Write(chr);
out.Write(chr);
}
Update
For anyone with an academic interest in batch scripting, I've posted a pure native batch version of tee at Asynchronous native batch tee script over at DosTips. But this hybrid approach is my preferred scripting solution.