bat несколько команд `start` с одновременным выводом stdout

У меня есть пакетный скрипт, который запускает несколько других программ (несколько экземпляров одного и того же), используя start Команда для распараллеливания задач, так как они независимы.

Однако команды выводят некоторые сообщения о состоянии в stdout это совершенно запутано. Есть ли способ обеспечить

  • а) хотя бы каждая строка печатается без прерывания выходов других задач - или -
  • б) выходные данные процессов сначала собираются и печатаются синхронизируются после их завершения? - или же
  • в) каким-то другим способом, о котором я еще не думал до сих пор.

2 ответа

Вы можете перенаправить вывод каждой команды в свой текстовый файл, используя start /wait на заключительной команде, затем прочитайте все текстовые файлы после.

@echo off
setlocal

for /L %%I in (1,1,5) do (
    start "" "powershell" -command "(Get-Date) -f ''" ^>output%%I.txt
)
start /wait "" "powershell" -command "(Get-Date) -f ''" ^>output6.txt

for /L %%I in (1,1,6) do (
    type output%%I.txt
    del output%%I.txt
)

vlad_tepesch прокомментировал:

последняя запущенная программа не обязательно является последней, которая выходит

Редактировать:

vlad_tepesch прокомментировал:

... упомянутый вами "список задач" дает мне некоторое представление: следите за списком задач и ждите, пока запущенные задачи исчезнут.

Отличная идея. Попробуй это:

@echo off
setlocal

for /L %%I in (1,1,6) do (
    start "" "powershell" -command "(Get-Date) -f ''" ^>output%%I.txt
)

:wait
tasklist | find /i "powershell" >NUL && goto wait

for /L %%I in (1,1,6) do (
    type output%%I.txt
    del output%%I.txt
)

Это просто доказательство концепции. Если вы создаете разные команды, просто замените

tasklist | find /i "powershell" >NUL && goto wait

с

tasklist | findstr /i "program1 program2 program3 program4 etc" >NUL && goto wait

... вставляя имя каждого из ваших порожденных исполняемых файлов в findstr,


старое решение JScript:

Более сложное управление асинхронными процессами лучше обрабатывается Windows Scripting Host (VBScript или JScript).

Ну, я полагаю, если вам не нужен вывод консоли приложения, то вы можете вызвать wmic process call create 'command' и захватить PID с for /f цикл, ожидая, пока PID исчезнет из списка задач. Это был бы способ достичь вашей цели с помощью чистой партии. Но нет никакого изящного способа запечатлеть command сюда.

Вместо этого лучше использовать объект JScript для сбора порожденных процессов с WshShell.Exec, Затем вы можете работать с выводом программы так, как вам нравится - записывать как в консоль, так и в файл журнала, если хотите, или только в один или другой.

@if (@CodeSection == @Batch) @then

@echo off
setlocal

rem // push list of tasks into jobs file
>"jobs.txt" (
    echo wmic nicconfig where "dhcpserver like '%%.%%'" get macaddress ^| find ":"
    echo ping -n 3 localhost
    echo ipconfig
)

rem // process jobs file with JScript
cscript /nologo /e:JScript "%~f0" "jobs.txt"
del "jobs.txt"

echo;
echo Done.  That was ossum!

rem // End main runtime
goto :EOF
@end
// begin JScript chimera

var fso = WSH.CreateObject('Scripting.FileSystemObject'),
    osh = WSH.CreateObject('Wscript.Shell'),
    jobsfile = fso.OpenTextFile(WSH.Arguments(0), 1),
    jobs = jobsfile.ReadAll().split(/\r?\n/),
    procs = {};

jobsfile.Close();

for (var i=0, end=jobs.length; i<end; i++) {
    procs[i] = osh.Exec('cmd /c ' + jobs[i]);
    WSH.Echo('PID ' + procs[i].ProcessID + ': ' + jobs[i]);
}
WSH.Echo('Working...\n');

// wait for all jobs to complete
while (1) {
    var complete = 0;
    for (var i in procs) if (procs[i].Status) complete++;
    if (complete == jobs.length) break;
    WSH.Sleep(1);
}

for (var i in procs) {
    if (!procs[i].StdOut.AtEndOfStream) WSH.StdOut.Write(procs[i].StdOut.ReadAll());
    if (!procs[i].StdErr.AtEndOfStream) WSH.StdErr.Write(procs[i].StdErr.ReadAll());
}

Это чисто пакетное решение, которое печатает полные строки из каждого параллельного процесса:

@echo off
setlocal EnableDelayedExpansion

rem If this program was re-executed to arbitrate output, do it
if "%~1" equ ":ArbitrateOutput" goto %1

rem Execute N parallel instances of other program
for /L %%i in (1,1,4) do (
   start /B "" "cmd /C otherProgram %%i  |  "%~F0" :ArbitrateOutput 2> NUL"
)
goto :EOF


rem Arbitrates concurrent output to screen from parallel processes
:ArbitrateOutput

rem Read a line from our companion parallel process
set "line=:EOF"
set /P "line="
if "%line%" equ ":EOF" exit

rem Try: to set an exclusive lock for unique output
:uniqueOutput
2> lock (
   rem Send the line to the screen and delete it
   echo %line%
   set "line="
)

rem Catch: if the line was not sent, try again
if defined line goto uniqueOutput

goto :ArbitrateOutput

Например, с этим otherProgram.bat:

@echo off

rem Get a random number of messages
set /A messages=%random% %% (%time:~-1%+1) + 1
ECHO INSTANCE %1: MESSAGES %MESSAGES%

for /L %%i in (1,1,%messages%) do (
   ping -n 2 localhost > NUL
   echo Greetings from instance number %1
)
exit

... это вывод:

C:\> test

C:\> INSTANCE 4: MESSAGES 3
INSTANCE 3: MESSAGES 4
INSTANCE 1: MESSAGES 2
INSTANCE 2: MESSAGES 5
Greetings from instance number 2
Greetings from instance number 1
Greetings from instance number 3
Greetings from instance number 4
Greetings from instance number 3
Greetings from instance number 2
Greetings from instance number 4
Greetings from instance number 1
Greetings from instance number 3
Greetings from instance number 2
Greetings from instance number 4
Greetings from instance number 3
Greetings from instance number 2
Greetings from instance number 2

Этот метод может потерять некоторые данные, если параллельный процесс отправляет несколько строк в одной и той же операции, то есть он лучше работает с одиночными строками, разделенными пробелом. Кроме того, он останавливается на первой пустой строке, выводимой из процесса. Эта последняя точка может быть исправлена, если требуется.

Другие вопросы по тегам