如何将stdout实时传递给批处理脚本

时间:2017-04-07 00:37:39

标签: windows batch-file pipe

我正在尝试将从程序输出的行传输到另一个脚本,该脚本将实时读取行并根据行中的内容执行某些操作。

例如,输出是

$program.exe 
Program Started
Processing info
Work Set Started
Work Set Ended
Results are..
Ending Program

我希望将上传程序输出“工作集开始”的脚本发送到可以触发另一个程序的脚本

我在trigger.bat

中尝试过以下操作
@echo off

SETLOCAL

FOR /F "tokens=1* delims=]" %%A IN ('FIND /N /V ""') DO (
    IF "%%B" == "Work Set Started" (
        ProgramB.exe
        )
)

ENDLOCAL

当我运行以下

$program.exe | trigger.bat

ProgramB.exe未实时启动,但来自trigger.bat的for循环在收集管道的所有输出后执行。

我做错了吗?我必须使用PowerShell吗?

2 个答案:

答案 0 :(得分:2)

FOR /F命令首先收集执行命令的所有输出,并开始处理每个输出行,直到命令结束。

您可以通过以下方式修改trigger.bat文件代码:

@echo off
SETLOCAL

:nextLine
set /P "line="
echo Line received: "%line%"
FOR /F "tokens=1* delims=]" %%A IN ("%line%") DO (
   IF "%%B" == "Work Set Started" ECHO ProgramB.exe
   IF "%%B" neq "Ending Program" GOTO NextLine
)

但是,如果$program.exe的输出生成得太快,可能会出现一些同步问题。我使用这个output.txt文件测试了这段代码:

[1]$program.exe 
[2]Program Started
[3]Processing info
[4]Work Set Started
[5]Work Set Ended
[6]Results are..
[7]Ending Program

如果我这样做:type output.txt | trigger.bat程序失败,但如果通过此output.bat程序缓慢生成输出行:

@echo off
setlocal EnableDelayedExpansion

for /F "delims=" %%a in (output.txt) do (
   set /A "rand=!random! %% 4 + 2"
   ping -n !rand! localhost > NUL
   echo %%a
)

...然后方法正常工作:

C:\> output.bat | trigger.bat
Line received: "[1]$program.exe "
Line received: "[2]Program Started"
Line received: "[3]Processing info"
Line received: "[4]Work Set Started"
ProgramB.exe
Line received: "[5]Work Set Ended"
Line received: "[6]Results are.."
Line received: "[7]Ending Program"

所以你需要根据你的具体情况测试这个方法......

答案 1 :(得分:1)

使用简单代码测试以生成输出

@echo off
    setlocal enableextensions disabledelayedexpansion

    >"programOutput.txt" (
        echo(Program Started
        echo(Processing info
        echo(Work Set Started
        echo(Work Set Ended
        echo(Results are..
        echo(Ending Program    
    )

    set program=findstr "^" programOutput.txt

    %program% | trigger.cmd

trigger.cmd代码可能类似于

@echo off
    setlocal enableextensions disabledelayedexpansion
    if "%~1"==".monitor." ( call :monitor ) else ( call :start )
    goto :eof

:start    
    echo .... Entering monitor
    for %%t in ("%temp%\%~n0.%random%%random%%random%%random%%random%.tmp") do (
        type nul >"%%~ft"
        findstr "^" > "%%~ft" | ( <"%%~ft" "%~f0" .monitor. & echo Start program B here )
    ) & 2>nul del /q "%%~ft"
    echo .... Main program ended - Leaving monitor
    goto :eof

:monitor
    rem Try to read data. In case of failure wait and repeat
    set /p "data=" || (
        >nul ping -n 2 "" 
        goto :monitor
    )
    echo(.... monitor has retrieved [%data%]
    if /i "%data%"=="Work Set Started" (
        echo(.... !!! : detected work set start. Leaving monitor
        goto :eof
    )
    rem No end of monitoring condition found. Try again
    goto :monitor

正如Aacini所说,

  • for /f的问题是在开始处理之前必须检索所有数据。
  • 管道内使用set /p的循环问题是管道两侧的同步。

但这个问题有一个解决方法。您可以使用中间文件。也就是说,您可以将命令的输出重定向到文件,并且在程序正在写入的同时,您可以从文件中读取。不是防弹但更稳定。

更安全的处理方式是使用某种方式指示每条线的起始位置。这是另一个trigger.cmd替代方法,它使用find /n命令处理监视循环中正在处理的程序的输出。如果输入缓冲区中的第一个字符是[,我们将假设我们找到了一行的开头,否则,它是一个续行。

@echo off
    setlocal enableextensions disabledelayedexpansion
    if "%~1"==".monitor." ( call :monitor ) else ( call :start )
    goto :eof

:start    
    echo .... Entering monitor
    set "buffer="
    for %%t in ("%temp%\%~n0.%random%%random%%random%%random%%random%.tmp") do (
        type nul >"%%~ft"
        find /n /v "" > "%%~ft" | ( <"%%~ft" "%~f0" .monitor. & echo Start program B here )
    ) & 2>nul del /q "%%~ft"
    echo .... Main program ended - Leaving monitor
    goto :eof


:monitor
    rem Try to read data. In case of failure wait and repeat
    set /p "data=" || (
        >nul ping -n 2 "" 
        goto :monitor
    )
    echo(.... monitor has retrieved [%data%]

    rem The output of the process is being numerated by find /n
    rem so each line is in the form [9]xxxxxxx
    if not "%data:~0,1%"=="[" (
        rem If we don not have a line start, append to previous buffer
        set "buffer=%buffer%%data%"
    ) else (
        rem New line found to process
        set "buffer=%data%"
    )

    rem Remove initial [number] data
    for /f "tokens=2 delims=]" %%a in ("%buffer%") do (
        echo(.... checking program output [%%a]
        if /i "%%a"=="Work Set Started" (
            echo(.... !!! detected work set start. Leaving monitor
            goto :eof
        )
    )

    rem No end of monitoring condition found. Try again
    goto :monitor

注意:虽然在编写上一段代码时并不知道,但我已经复制了dbenham的工作(而不是最好的方式)。请参阅this post,了解相同想法的改进版本。