Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a custom Tee command for .bat file

I am trying to use the tee code written for a bat file but am having trouble implementing it in my code. I don't want to use any third party installs to solve the tee problem as I want the code to work if I format my computer in a year and want to run the program again.

I have it setup in this fashion:

mycommand.exe | tee.bat -a output.txt

I've tried with a seperate .bat file and tried including as a function (preffered) in the original .bat to no avail with:

myprogram.exe | call tee -a output.txt
echo.
echo.
echo.
SET /P restart="Do you want to run again? (1=yes, 2=no): "
if "%restart%"=="1" GOTO LoopStart


::--------------------------------------------------------
::-- Function section starts below here
::--------------------------------------------------------
:tee
:: Check Windows version
IF NOT "%OS%"=="Windows_NT" GOTO Syntax
| 
:: Keep variables local
SETLOCAL

:: Check command line arguments
SET Append=0
IF /I [%1]==[-a] (
    SET Append=1
    SHIFT
)
IF     [%1]==[] GOTO Syntax
IF NOT [%2]==[] GOTO Syntax

:: Test for invalid wildcards
SET Counter=0
FOR /F %%A IN ('DIR /A /B %1 2^>NUL') DO CALL :Count "%%~fA"
IF %Counter% GTR 1 (
    SET Counter=
    GOTO Syntax
)

:: A valid filename seems to have been specified
SET File=%1

:: Check if a directory with the specified name exists
DIR /AD %File% >NUL 2>NUL
IF NOT ERRORLEVEL 1 (
    SET File=
    GOTO Syntax
)

:: Specify /Y switch for Windows 2000 / XP COPY command
SET Y=
VER | FIND "Windows NT" > NUL
IF ERRORLEVEL 1 SET Y=/Y

:: Flush existing file or create new one if -a wasn't specified
IF %Append%==0 (COPY %Y% NUL %File% > NUL 2>&1)

:: Actual TEE
FOR /F "tokens=1* delims=]" %%A IN ('FIND /N /V ""') DO (
    >  CON    ECHO.%%B
    >> %File% ECHO.%%B
)

:: Done
ENDLOCAL
GOTO:EOF


:Count
SET /A Counter += 1
SET File=%1
GOTO:EOF


:Syntax
ECHO.
ECHO Tee.bat,  Version 2.11a for Windows NT 4 / 2000 / XP
ECHO Display text on screen and redirect it to a file simultaneously
ECHO.
IF NOT "%OS%"=="Windows_NT" ECHO Usage:  some_command  ¦  TEE.BAT  [ -a ]  filename
IF NOT "%OS%"=="Windows_NT" GOTO Skip
ECHO Usage:  some_command  ^|  TEE.BAT  [ -a ]  filename
:Skip
ECHO.
ECHO Where:  "some_command" is the command whose output should be redirected
ECHO         "filename"     is the file the output should be redirected to
ECHO         -a             appends the output of the command to the file,
ECHO                        rather than overwriting the file
ECHO.
ECHO Written by Rob van der Woude
ECHO http://www.robvanderwoude.com
ECHO Modified by Kees Couprie
ECHO http://kees.couprie.org
ECHO and Andrew Cameron

I am trying to split the output so I can save the console output to a file while still being able to interact with the program that is running.

How can I get the Tee command to work properly with my .bat so I can split the output to both a file and the console.

like image 213
Nick Avatar asked May 23 '12 00:05

Nick


People also ask

How do you create a batch file that uses a command?

To create a Windows batch file, follow these steps: Open a text file, such as a Notepad or WordPad document. Add your commands, starting with @echo [off], followed by, each in a new line, title [title of your batch script], echo [first line], and pause. Save your file with the file extension BAT, for example, test.

Is there a tee command for Windows?

The `tee' command copies standard input to standard output and also to any files given as arguments. This is useful when you want not only to send some data down a pipe, but also to save a copy.

Are .bat and .cmd files the same?

CMD files have the current version of Microsoft language while BAT has the older version of the Microsoft language. CMD is backward compatible while BAT is not backward compatible. CMD runs in most command.com scripts whereas BAT by itself will not run in command.com scripts unless made to do so.


1 Answers

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.

like image 195
dbenham Avatar answered Sep 23 '22 19:09

dbenham