batch-file

Processing command output on batch before command finished


I try to print time of command output(ping as example).

echo([!time!] start
for /f "usebackq delims= eol=" %%A in (`ping ::1`) do (
  echo([!time!] __ %%A
)
echo([!time!] finish

but as result I got

[12:37:08.78] start
[12:37:12.04] __ Pinging ::1 with 32 bytes of data:
[12:37:12.04] __ Reply from ::1: time<1ms
[12:37:12.04] __ Reply from ::1: time<1ms
[12:37:12.04] __ Reply from ::1: time<1ms
[12:37:12.04] __ Reply from ::1: time<1ms
[12:37:12.04] __ Ping statistics for ::1:
[12:37:12.04] __     Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
[12:37:12.04] __ Approximate round trip times in milli-seconds:
[12:37:12.04] __     Minimum = 0ms, Maximum = 0ms, Average = 0ms
[12:37:12.04] finish

look like cycle start work only after command finished. Is there a way to process command output before command finished?


Solution

  • The FOR/F can not be used for asynchronous processing.

    But it can be solved by using two threads and a pipe.
    The producer_thread outputs to a file and the consumer_thread reads all the data with set /p and stops when it finds an "EOF" string.

    To differentiate between the real "EOF" and an "EOF" from program output, the program output is piped through FIND to prefix each line with a line number.

    The multiple for /L %%n in (1,1,256) do are used to build an (nearly) infinite loop, but still to be able to exit the loop with a goto command (a really infinite loop can't be stopped with goto).

    @echo off
    REM *** Trampoline jump for function calls of the form ex. "C:\:functionName:\..\myBatch.bat"
    FOR /F "tokens=3 delims=:" %%L in ("%~0") DO goto :%%L
    
    cd. > async.tmp
    
    "%~d0\:producer_thread:\..\%~pnx0" >> async.tmp | "%~d0\:consumer_thread:\..\%~pnx0" < async.tmp
    echo END
    exit /b
    
    :producer_thread
    (
        ping ::1 -n 3
        echo Test a single EOT
        echo EOT
        echo Some other output
    ) | find /n /v ""
    (echo EOF)
    exit /b
    
    :consumer_thread
    setlocal EnableDelayedExpansion
    
    echo([!time!] start
    for /L %%n in (1,1,256) do for /L %%n in (1,1,256) do for /L %%n in (1,1,256) do for /L %%n in (1,1,256) do (
      set "line="
      set /p "line="
      if "!line!" == "EOF" goto :exit_loop
      if defined line (
        set "line=!line:*]=!"
        echo([!time!] __ !line!
      )
    )
    :exit_loop
    echo([!time!] finish
    exit /b