#1 18 Feb 2014 01:23

dbenham
Member
From: U.S. east coast
Registered: 15 Apr 2012
Posts: 100

Non-blocking asynchronous batch implementation of tee

I've managed to write a non-blocking asynchronous batch implementation of tee that uses only internal batch commands plus the FIND native external command.

It writes all output to a temporary file in the %TEMP% folder, and simultaneously reads from that same file using SET /P. The temp file name incorporates the current time to 0.1 seconds, and the script automatically loops back to try again if another process happens to grab the same temp name.

batchTee.bat performs quite well, though there are plenty of free Windows ports of the unix utility that perform better. There is also a Hybrid JScript/batch that performs better. But this was an interesting problem to solve using pure batch.

There are a number of limitations that are listed in the documentation at the top of the script.

batchTee.bat

::batchTee.bat  OutputFile  [+]
::
::  Write each line of stdin to both stdout and outputFile.
::  The default behavior is to overwrite any existing outputFile.
::  If the 2nd argument is + then the content is appended to any existing
::  outputFile.
::
::  Limitations:
::
::  1) Lines are limited to ~1000 bytes. The exact maximum line length varies
::     depending on the line number. The SET /P command is limited to reading
::     1021 bytes per line, and each line is prefixed with the line number when
::     it is read.
::
::  2) Trailing control characters are stripped from each line.
::
::  3) Lines will not appear on the console until a newline is issued, or
::     when the input is exhaused. This can be a problem if the left side of
::     the pipe issues a prompt and then waits for user input on the same line.
::     The prompt will not appear until after the input is provided.
::

@echo off
setlocal enableDelayedExpansion
if "%~1" equ ":tee" goto :tee

:lock
set "teeTemp=%temp%\tee%time::=_%"
2>nul (
  9>"%teeTemp%.lock" (
    for %%F in ("%teeTemp%.test") do (
      set "yes="
      pushd "%temp%"
      copy /y nul "%%~nxF" >nul
      for /f "tokens=2 delims=(/" %%A in (
        '^<nul copy /-y nul "%%~nxF"'
      ) do if not defined yes set "yes=%%A"
      popd
    )
    for /f %%A in ("!yes!") do (
      find /n /v ""
      echo :END
      echo %%A
    ) >"%teeTemp%.tmp" | <"%teeTemp%.tmp" "%~f0" :tee %* 7>&1 >nul
    (call )
  ) || goto :lock
)
del "%teeTemp%.lock" "%teeTemp%.tmp" "%teeTemp%.test"
exit /b

:tee
set "redirect=>"
if "%~3" equ "+" set "redirect=>>"
8%redirect% %2 (call :tee2)
set "redirect="
(echo ERROR: %~nx0 unable to open %2)>&7

:tee2
for /l %%. in () do (
  set "ln="
  set /p "ln="
  if defined ln (
    if "!ln:~0,4!" equ ":END" exit
    set "ln=!ln:*]=!"
    (echo(!ln!)>&7
    if defined redirect (echo(!ln!)>&8
  )
)

Usage is as expected for a tee utility:

dir | batchTee output.txt

The above will overwrite any existing output.txt.

Content can be appended to an existing file by adding + as a second argument:

dir | batchTee output.txt +

EDIT - I've made many changes to the code above. Development history is available at http://www.dostips.com/forum/viewtopic.php?f=3&t=5386


Dave Benham

Last edited by dbenham (20 Feb 2014 15:56)

Offline

Board footer

Powered by FluxBB