Как кодировать счетчик для ожидающих процессов в пакетном файле? - PullRequest
13 голосов
/ 15 декабря 2008

Я хотел бы показать пользователю с помощью счетчика, что что-то делается в фоновом режиме, но не знаю, как это работает в пакетном файле.

У кого-нибудь есть подсказка?

Ответы [ 12 ]

23 голосов
/ 16 декабря 2008

На самом деле это можно сделать довольно просто с помощью чисто нативных команд, вам просто нужно знать, как использовать более сложные из них. Нет необходимости использовать внешние инструменты, такие как VBScript или неприятные побочные эффекты, такие как очистка экрана.

То, что вы ищете, является эквивалентом команды bash "echo -n", которая выводит строку без перевода строки. В пакетном режиме XP это достигается с помощью «set /p» (запросить у пользователя ответ с приглашением) с пустым вводом следующим образом:

<nul (set /p junk=Hello)
echo. again.

выведет строку «Привет еще раз». без ввода новой строки.

Этот трюк (и использование CTRL-H, символа возврата на задний план можно увидеть в следующем тестовом скрипте, который запускает (одну за другой) 10-секундную подзадачу с 20-секундным тайм-аутом и 15-секундным вторая подзадача с 10-секундным таймаутом.

Сценарий полезной нагрузки создается текущим выполняемым сценарием, и его единственным требованием является то, что он выполняет ту работу, которую ему необходимо сделать, а затем удаляет файл флага, когда он закончил, чтобы функция монитора могла его обнаружить.

Имейте в виду, что строки ^ H в этом скрипте на самом деле являются символами CTRL-H, символ ^ | это два отдельных символа, используемые для экранирования символа трубы.

@echo off

:: Localise environment.
setlocal enableextensions enabledelayedexpansion

:: Specify directories. Your current working directory is used
:: to create temporary files tmp_*.*
set wkdir=%~dp0%
set wkdir=%wkdir:~0,-1%

:: First pass, 10-second task with 20-second timeout.
del "%wkdir%\tmp_*.*" 2>nul
echo >>"%wkdir%\tmp_payload.cmd" ping 127.0.0.1 -n 11 ^>nul
echo >>"%wkdir%\tmp_payload.cmd" del "%wkdir%\tmp_payload.flg"
call :monitor "%wkdir%\tmp_payload.cmd" "%wkdir%\tmp_payload.flg" 20

:: Second pass, 15-second task with 10-second timeout.
del "%wkdir%\tmp_*.*" 2>nul:
echo >>"%wkdir%\tmp_payload.cmd" ping 127.0.0.1 -n 16 ^>nul
echo >>"%wkdir%\tmp_payload.cmd" del "%wkdir%\tmp_payload.flg"
call :monitor "%wkdir%\tmp_payload.cmd" "%wkdir%\tmp_payload.flg" 10

goto :final

:monitor
    :: Create flag file and start the payload minimized.
    echo >>%2 dummy
    start /min cmd.exe /c "%1"

    :: Start monitoring.
    ::    i is the indicator (0=|,1=/,2=-,3=\).
    ::    m is the number of seconds left before timeout.
    set i=0
    set m=%3
    <nul (set /p z=Waiting for child to finish: ^|)

    :: Loop here awaiting completion.
    :loop
        :: Wait one second.
        ping 127.0.0.1 -n 2 >nul

        :: Update counters and output progress indicator.
        set /a "i = i + 1"
        set /a "m = m - 1"
        if %i% equ 4 set i=0
        if %i% equ 0 <nul (set /p z=^H^|)
        if %i% equ 1 <nul (set /p z=^H/)
        if %i% equ 2 <nul (set /p z=^H-)
        if %i% equ 3 <nul (set /p z=^H\)

        :: End conditions, complete or timeout.
        if not exist %2 (
            echo.
            echo.   Complete.
            goto :final
        )
        if %m% leq 0 (
            echo.
            echo.   *** ERROR: Timed-out waiting for child.
            goto :final
        )
        goto :loop
:final
endlocal
5 голосов
/ 15 октября 2012

Попробуйте это:

@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
CALL :BACKSPACE $BS
SET /A FULL_COUNT=60
SET /A MAX_COUNT=160
SET /A Spin_Delay=50
SET "_MSG=Process running..."
SET /A CTR=0
SET /A TCT=0
IF NOT [%1]==[] SET _MSG=%~1
IF NOT [%2]==[] SET /A FULL_COUNT=%2
IF NOT [%3]==[] SET /A SPIN_DELAY=%3
IF %FULL_COUNT% GTR %MAX_COUNT% SET FULL_COUNT=%MAX_COUNT%
(SET/P=%_MSG%*)<nul
FOR /L %%A IN (1,1,%FULL_COUNT%) DO (
  CALL :DELAY %SPIN_DELAY%
  IF !CTR! EQU 0 (set/p=%$BS%³)<nul
  IF !CTR! EQU 1 (set/p=%$BS%/)<nul
  IF !CTR! EQU 2 (set/p=%$BS%Ä)<nul
  IF !CTR! EQU 3 (set/p=%$BS%\)<nul
  SET /A CTR=%%A %% 4
)
(SET/P=%$BS%*)<nul
ENDLOCAL & EXIT /B %CTR%

:BackSpace
setlocal
for /f %%a in ('"prompt $H$S &echo on &for %%b in (1) do rem"') do set "Bs=%%a"
endlocal&call set %~1=%BS%&exit /b 0

:Delay msec
setlocal enableextensions
set/a correct=0
set/a msecs=%1+5
if /i %msecs% leq 20 set /a correct-=2
set time1=%time: =%
set/a tsecs=%1/1000 2>nul
set/a msecs=(%msecs% %% 1000)/10
for /f "tokens=1-4 delims=:." %%a in ("%time1%") do (
  set hour1=%%a&set min1=%%b&set sec1=%%c&set "mil1=%%d"
)
if /i %hour1:~0,1% equ 0 if /i "%hour1:~1%" neq "" set hour1=%hour1:~1% 
if /i %min1:~0,1% equ 0 set min1=%min1:~1% 
if /i %sec1:~0,1% equ 0 set sec1=%sec1:~1%
if /i %mil1:~0,1% equ 0 set mil1=%mil1:~1% 
set/a sec1+=(%hour1%*3600)+(%min1%*60)
set/a msecs+=%mil1%
set/a tsecs+=(%sec1%+%msecs%/100)
set/a msecs=%msecs% %% 100
::    check for midnight crossing
if /i %tsecs% geq 86400 set /a tsecs-=86400
set/a hour2=%tsecs% / 3600
set/a min2=(%tsecs%-(%hour2%*3600)) / 60
set/a sec2=(%tsecs%-(%hour2%*3600)) %% 60
set/a err=%msecs%
if /i %msecs% neq 0 set /a msecs+=%correct%
if /i 1%msecs% lss 20 set msecs=0%msecs%
if /i 1%min2% lss 20 set min2=0%min2%
if /i 1%sec2% lss 20 set sec2=0%sec2%
set time2=%hour2%:%min2%:%sec2%.%msecs%
:wait
  set timen=%time: =%
  if /i %timen% geq %time2% goto :end
goto :wait
:end
for /f "tokens=2 delims=." %%a in ("%timen%") do set num=%%a
if /i %num:~0,1% equ 0 set num=%num:~1%
set/a err=(%num%-%err%)*10
endlocal&exit /b %err%
4 голосов
/ 15 декабря 2008

Если вы не возражаете против очистки экрана ... попробуйте это:

@ECHO OFF

SETLOCAL ENABLEDELAYEDEXPANSION
SET COUNT=1

START CALC

:BEGIN
  CLS
  IF !COUNT! EQU 1 ECHO \
  IF !COUNT! EQU 2 ECHO -
  IF !COUNT! EQU 3 ECHO /
  IF !COUNT! EQU 4 ECHO -
  IF !COUNT! EQU 4 (
    SET COUNT=1
  ) ELSE (
    SET /A COUNT+=1
  )
  PSLIST CALC >nul 2>&1
  IF %ERRORLEVEL% EQU 1 GOTO END
GOTO BEGIN

:END

РЕДАКТИРОВАТЬ: этот образец запускает калькулятор и затем отображает «счетчик», пока вы не закроете калькулятор. Я использую pslist , чтобы проверить существование CALC.EXE. > nul 2> & 1 перенаправляет STDOUT и STDERR на nul, поэтому ничего из PSLIST отображаться не будет.

2 голосов
/ 15 декабря 2008

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

В этом случае, насколько я знаю, это невозможно с нативными командами. (это могло бы быть возможно, если бы у вас была программа, которая показывала счетчик при выполнении операции, которая занимает много времени)

И похоже, что echo не поддерживает escape-последовательности ansi (в старые времена вам приходилось загружать ansi.sys, не знаю, существует ли он по-прежнему), поэтому вы не можете использовать ansi для управления курсором .

1 голос
/ 15 декабря 2008

Вращатель МОЖЕТ быть выполнен в пакетном сценарии, вам просто нужно несколько переменных:

@echo off

:spinner
set mSpinner=%mSpinner%.
if %mSpinner%'==..............................' set mSpinner=.
cls
echo %mSpinner%

rem Check if the process has finished via WMIC and/or tasklist.

goto spinner


:exit

Для того, чтобы сама BAT обнаружила, что процесс запущен / завершен. Вы можете сделать это через интерфейс командной строки WMI или команду списка задач, о которой у меня ограниченные знания.

Если бы это было в дни DOS, вы могли бы сделать это даже без очистки экрана ... если не использовать некоторую комбинацию escape-символов. Я не знаю, возможно ли это в Vista / XP.

1 голос
/ 15 декабря 2008

Если вы имеете в виду пакетный скрипт Windows, вы не можете сделать это изначально. Оператор echo, используемый для печати на консоли, всегда печатает новую строку, и вы не можете перемещать курсор.

Это что-то вроде хака, но вы можете сделать это с помощью комбинации VBScript и пакетного скрипта.

Этот VBScript напечатает клавишу возврата, затем его аргумент:

WScript.StdOut.Write(chr(8) & WScript.Arguments(0))

Поместите это в файл, vbsEcho.vbs, затем вызовите этот скрипт из вашего пакетного скрипта. Следующий пакетный скрипт будет отображать счетчик пока вы не нажмете CTRL-C:

@echo off

:LOOP
cscript //nologo vbsEcho.vbs "\"
cscript //nologo vbsEcho.vbs "|"
cscript //nologo vbsEcho.vbs "/"
cscript //nologo vbsEcho.vbs "-"
goto :LOOP

РЕДАКТИРОВАТЬ: Используя некоторые идеи из ответа aphoria, этот скрипт запустит калькулятор Windows и покажет счетчик, пока калькулятор не закроется:

@ECHO OFF

SETLOCAL ENABLEDELAYEDEXPANSION
SET COUNT=1

START CALC

cscript //nologo vbsEcho.vbs "Calculating: \"
:LOOP
IF !COUNT! EQU 1 cscript //nologo vbsEcho.vbs "|"
IF !COUNT! EQU 2 cscript //nologo vbsEcho.vbs "/"
IF !COUNT! EQU 3 cscript //nologo vbsEcho.vbs "-"
IF !COUNT! EQU 4 (
    cscript //nologo vbsEcho.vbs "\"
    set COUNT=1
) else (
    set /a COUNT+=1
)

pslist CALC >nul 2>&1
if %ERRORLEVEL% EQU 1 goto :end

goto :LOOP

:END
cscript //nologo vbsEcho.vbs ". Done."
0 голосов
/ 03 августа 2017

запустить приложение, дождаться загрузки

@echo off & setlocal enabledelayedexpansion
start application.exe
:1
for %%a in (^| ^/ ^- ^\ ^| ^/ ^- ^\) do (
for %%b in (^| ^/ ^- ^\ ^| ^/ ^- ^\) do (
for %%c in (^| ^/ ^- ^\ ^| ^/ ^- ^\) do (
cls &echo processing..%%c%%b%%a
sleep -m 20
IF EXIST "result file" (exit)
)))
goto 1
0 голосов
/ 12 марта 2015

Эта процедура проверяет вывод списка задач для процесса, который вы запускаете из cmd.
Передайте ему имя exe в качестве параметра, например.
вызов: spinner calc.exe
Сообщает
Прошло: 001 секунд
и увеличивает секунды до тех пор, пока процесс exe не завершится.
Сообщение «Прошедшие 001 секунда» перезаписывается ECHO.exe каждую секунду.
который повторяет CR без перевода строки.
Echo.exe доступен по адресу http://www.paulsadowski.com/wsh/cmdprogs.htm

 @echo off
 start calc
 call :spinner calc.exe
 pause

 :spinner       
 SET COUNT=1
 :BEGIN
 set "formattedValue=000000%count%"
 ECHO.exe -n Elapsed: %formattedValue:~-3% seconds
 ECHO.exe -n \r    %= -n (suppress crlf) \r output a cr =%

 SET /A COUNT+=1

 set EXE=%1    %= search output of tasklist for EXE =%
 set tl=tasklist /NH /FI "IMAGENAME eq %EXE%"
 FOR /F %%x IN ('%tl%') DO IF %%x == %EXE% goto FOUND
 set result=0
 goto FIN
 :FOUND
 set result=1
 :FIN
 IF %result% EQU 0 GOTO END

 PING -n 2 127.0.0.1 > nul    %= wait for about 1 second =%

 GOTO BEGIN
 :END
0 голосов
/ 17 января 2010

У paxdiablos отличный ответ, но необходимость выводить ваши команды в файл полезной нагрузки раздражает. Трудно читать и трудно отлаживать. Я взял его код и немного его изменил для собственного использования:

@echo off

:: Localise environment.
setlocal enableextensions enabledelayedexpansion

set wkdir=%~dp0%
set wkdir=%wkdir:~0,-1%
set done_flag="%wkdir%\tmp_payload.flg"
set timeout=7


:controller

IF (%1)==() (
    call :monitor step1 "Getting stuff from SourceSafe: "
    call :monitor step2 "Compiling some PHP stuff: "
    call :monitor step3 "Finishing up the rest: "
) ELSE ( goto %1 )

goto final


:step1
::ping for 5 seconds
    ping 127.0.0.1 -n 6 >nul
    del "%wkdir%\tmp_payload.flg"
goto final

:step2
::ping for 10 seconds
    ping 127.0.0.1 -n 11 >nul
    del "%wkdir%\tmp_payload.flg"
goto final

:step3
::ping for 5 seconds
    ping 127.0.0.1 -n 6 >nul
    del "%wkdir%\tmp_payload.flg"
goto final

:monitor
:: Create flag file and start the payload minimized.
:: echo the word "dummy" to the flag file (second parameter)
    echo >>%done_flag% dummy
:: start the command defined in the first parameter
    start /min cmd.exe /c "test2.bat %1"

:: Start monitoring.
::    i is the indicator (0=|,1=/,2=-,3=\).
::    m is the number of seconds left before timeout.
set i=0
set m=%timeout%
set str=%2
for /f "useback tokens=*" %%a in ('%str%') do set str=%%~a

<nul (set /p z=%str%^|)

:: Loop here awaiting completion.
:loop
    :: Wait one second.
    ping 127.0.0.1 -n 2 >nul

    :: Update counters and output progress indicator.
    set /a "i = i + 1"
    set /a "m = m - 1"
    if %i% equ 4 set i=0
    if %i% equ 0 <nul (set /p z=^|)
    if %i% equ 1 <nul (set /p z=/)
    if %i% equ 2 <nul (set /p z=-)
    if %i% equ 3 <nul (set /p z=\)

    :: End conditions, complete or timeout.
    if not exist %done_flag% (
        ::echo.
        echo Complete
        goto :final
    )
    if %m% leq 0 (
        echo.
        echo.   *** ERROR: Timed-out waiting for child.
        goto :final
    )
    goto :loop

:final
endlocal
0 голосов
/ 12 августа 2009

Я считаю, что самый простой способ - обновить заголовок, чтобы вам не приходилось делать CLS все время.

Причина двух строк команды ping -n заключается в том, что для команды ping быстрее выполнять двойной пинг по секунде каждый, а не один пинг в две секунды.

Кроме того, для тех, кто не знает, a :: - это то же самое, что и REM, за исключением того, что комментарии игнорируются в начале парсера (я думаю, что это правильное слово), а не в конце. Проще говоря, эта строка игнорируется.

:: begin spin.cmd
@echo off
setlocal

set COUNT=0
set MAXCOUNT=10
set SECONDS=1

:LOOP
title "\"
call :WAIT
title "|"
call :WAIT
title "/"
call :WAIT
title "-"
if /i "%COUNT%" equ "%MAXCOUNT%" goto :EXIT
set /a count+=1
echo %COUNT%
goto :LOOP

:WAIT
ping -n %SECONDS% 127.0.0.1 > nul
ping -n %SECONDS% 127.0.0.1 > nul
goto :EOF

:EXIT
title FIN!
endlocal
:: end spin.cmd
...