How to code a spinner for waiting processes in a Batch file?
Asked Answered
S

13

18

I would like to show the user with a spinner, that something is done in background but do not know how this works in a batchfile.

Does anyone have a clue?

Sldney answered 15/12, 2008 at 11:10 Comment(3)
What do you mean by "In Batch"? Do you mean a .bat or .cmd file?Subclavius
From the poster's other questions, it seems he means from a .bat file.Chefoo
Correct, should be in a command file.Sldney
Y
27

This can actually be done quite easily with pure native commands, you just have to know how to use the more tricky of them. No use of external tools like VBScript or nasty side effects like clearing the screen are necessary.

What you're looking for is the equivalent of the bash "echo -n" command which outputs a line without the newline. In XP batch, this is achieved by using "set /p" (ask user for response with a prompt) with empty input as follows:

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

will output the string "Hello again." with no intervening newline.

That trick (and the use of CTRL-H, the backspace character can be seen in the following test script which starts (one after the other) a 10-second sub-task with a 20-second timeout and a 15-second sub-task with a 10-second timeout.

The payload script is created by the actual running script and its only requirement is that it do the work it has to do then delete a flag file when finished, so that the monitor function will be able to detect it.

Keep in mind that the ^H strings in this script are actually CTRL-H characters, the ^| is two separate characters used to escape the pipe symbol.

@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
Ylla answered 16/12, 2008 at 13:19 Comment(6)
How do you get the backspace character (CTRL-H) into the script?Layout
hey, that's old school, exactly what i hoped to read. At the moment the script does really nothing, but I think I'll get this to work later...Sldney
@Patrick, I use gVim for my editing so CTRL-V, CRTL-H is the key sequence.Ylla
@BeowulfOF, you can modify the payload to do whatever you want - the original installed Eclipse CDT and JDT on Windows.Ylla
Awesome skills! I'd give you a batch ninja badge if I can. Now I know how to put my ansi.sys days to good use :-)Bowen
For folks using Notepad++, you can insert the backspace character by going Edit -> Character Panel, and then double-clicking Value 8 "BS". This inserts the backspace special character.Scurrility
G
4

If you don't mind the screen clearing...try this:

@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

EDIT: This sample will start Calculator and then display a "spinner" until you close Calculator. I use pslist to check for the existence of CALC.EXE. The >nul 2>&1 redirects STDOUT and STDERR to nul so nothing from PSLIST will be displayed.

Gaudreau answered 15/12, 2008 at 16:20 Comment(2)
might as well add some more description about pslistBowen
You could just as easily use tasklist instead of pslist which uses a readily available program.Clerk
O
4

Try this:

@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%
Osanna answered 15/10, 2012 at 8:4 Comment(1)
The only script that worked for me as expected on a Windows 7 machineParticipial
S
2

If I understand your question you want a spinner because some operation you are performing is taking time and you want to show to the user that something is happening, right?

In that case, as far as I know, its not possible with the native commands. (it could be possible if you had a program that showed a spinner while executing the operation that take long time)

And it looks like the echo don't support ansi escape sequences (in the old days you had to have ansi.sys loaded, don't know if that still exists) so you can't use ansi to control the cursor.

Squireen answered 15/12, 2008 at 13:16 Comment(0)
B
2

The spinner CAN be done in batch script, you just need some variables:

@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

For the BAT itself to detect a process running/exits. You can do that via the WMI command-line interface or the tasklist command of which I have limited knowledge.

If it were back in the DOS days you could even does that without clearing the screen... short of using some combination of escape characters. I don't know if it's still possible on Vista/XP.

Bowen answered 15/12, 2008 at 16:11 Comment(4)
Would you be so kind to remember the escape sequences, just to give it a try?Sldney
The escape sequences no longer work in standard cmd.exe windows, I've tried ESC[2A before to move up two lines for exactly this sort of progress bar project, to no avail.Ylla
You have to add "DEVICE=%systemroot%\system32\ANSI.SYS" to your system32\config.nt file and reboot. But it's not necessary, you can achieve it simpler than by including a new device driver. See my answer elsewhere in this question.Ylla
@Pax that's so ancient! havn't touched the config.nt in ages!Bowen
L
1

If you mean within a Windows batch script, you can't do it natively. The echo statement used to print to the console will always print a newline, and you can't move the cursor.

It's a bit of a hack, but you can do this with a combination of VBScript and batch script.

This VBScript will print a backspace, then it's argument:

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

Put this in a file, vbsEcho.vbs, then call this script from your batch script. The following batch script will keep displaying the spinner until you press CTRL-C:

@echo off

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

EDIT: Using some of the ideas from aphoria's answer, this script will start the Windows calculator, and display a spinner until the calculator closes:

@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."
Layout answered 15/12, 2008 at 15:55 Comment(0)
A
1

paxdiablos has an amazing answer, but having to echo your commands into a payload file is annoying. It's hard to read and hard to debug. I took his code and modified it a bit for my own use:

@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
Arterio answered 17/1, 2010 at 6:33 Comment(0)
I
0

You can use a counter that prints a different character from a given set (like "\|/-") and you change the character according to like "counter modulo 4". Anyway, you don't say in which language you're working in so it is a bit difficult to be more precise.

EDIT: now that we know in which environment you're playing in, I'd say that the BAT/CMD language is not really up to the task... I'd recommend any scripting language, Ruby being my favorite.

Interview answered 15/12, 2008 at 11:12 Comment(4)
I need to do this in batch, if possible.Sldney
batch is rather an environment than a language ;-)Johnston
Ask me, Batch is neither a language, than an environment. Its a sickness at best ;-)Sldney
No, "batch" is the use of the cmd.exe language. And it's come a long way since the bad old MSDOS days, it's actually quite powerful nowadays, @BeowulfOF.Ylla
H
0

I find the easiest way is to update the title - that way you don't have to do a CLS all the time.

The reason for the two lines of ping -n, is that it's quicker for ping to do a double ping of a second each, versus a single ping of two seconds.

Also, for those who don't know, a :: is the same as a REM, except that the comments are ignored at the beginning of the parser (I think this is the right word) instead of at the end. Simply put, that line is ignored.

:: 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
Hornblende answered 12/8, 2009 at 18:33 Comment(0)
G
0

This routine examines the output of tasklist for a process you START from cmd.
Pass it the name of the exe as a parameter e.g.
call :spinner calc.exe
It reports
Elapsed: 001 seconds
and increments seconds until the exe process terminates.
The Elapsed 001 seconds message is overwritten each second by ECHO.exe -n \r
which echos a cr without a line feed.
Echo.exe is available at 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
Guff answered 12/3, 2015 at 1:2 Comment(0)
F
0

start application, wait for loading

@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
Fernald answered 2/8, 2017 at 21:2 Comment(0)
M
0

Unsure if this is useful to anyone nowadays, but this was my working solution for a spinner in DOS batch (commenting inline).

This prints an inplace "spinner" with a defined X second delay between each charactere of the spinner.

This works in MS-DOS for Windows 10 and Windows 11.

@echo off

:: ===============================================
:: The below code will print a "spinner" 
:: in DOS batch with a defined delay (in seconds) 
:: between spinner character change.
::
:: This is a "copy and paste" code example that 
:: works in MS-DOS under Windows 10 and 11
:: ===============================================

:: ensure enabledelayedexpansion is set
setlocal enabledelayedexpansion

:: Define new variable %BS% to contain a backspace
for /f %%a in ('"prompt $H&for %%b in (1) do rem"') do set "BS=%%a"
:: the variable %BS% is now a backspace

::set the spinner characters - note that "pipe" char must be escaped... ie. ^|
set spinner_characters=\^|/-

::set the spinner character position (0 to 4)
set spinner_char_position=0

:: set the amount of seconds to delay between spinner character change output
set /A spinner_delay=1

:LOOP_SPINNER
:: load spinner_char as current spinner character for echo output
set spinner_char=!spinner_characters:~%spinner_char_position%,1!

:: increase spinner_char_position number, reset at 4
set /a spinner_char_position=(%spinner_char_position% + 1) %% 4

:: output spinner character with no newline and erase any prior character
echo | set /p output_spinner_char="%spinner_char%%BS%"

:: perform delay in seconds
set /A spinner_delay_temp=%spinner_delay%+1
ping localhost -n %spinner_delay_temp% > nul

:: you could add whatever logic you want here, and if required cndition is met, exit out of the spinner... goto END

goto LOOP_SPINNER

endlocal

:END
Monitor answered 10/2 at 4:7 Comment(1)
No, this certainly does NOT run in ANY version of MS-DOS. The Windows Command Line (cmd) (cmd.exe) is different from DOS (command.com). Although they look quite the same, cmd has some extensions, DOS didn't have and your answer depends on some of those extensions. (The answer is working in cmd, it's just the explaining text that is misleading and technically wrong)Corneous
A
-1
:LOOP
  ECHOX -n "~r%Processing..."
  IF %CTR% EQU 4 SET /A CTR=0
  IF %CTR%==0 (set /p DOT=³)<NUL
  IF %CTR%==1 (set /p DOT=/)<NUL
  IF %CTR%==2 (set /p DOT=Ä)<NUL
  IF %CTR%==3 (set /p DOT=\)<NUL
  ECHOX -n "~r"
  SET /A CTR+=1
  SET /A TCT+=1
  IF %TCT% GTR %MAX_COUNT% GOTO :END
GOTO :LOOP
Agraffe answered 26/5, 2009 at 4:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.