tags:

views:

1609

answers:

9

Hi there,

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?

A: 

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.

Keltia
I need to do this in batch, if possible.
BeowulfOF
batch is rather an environment than a language ;-)
0xA3
Ask me, Batch is neither a language, than an environment. Its a sickness at best ;-)
BeowulfOF
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.
paxdiablo
+2  A: 

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.

some
+1 for the good ole ansi.sys
chakrit
@chakrit: \e[0;1;37;41mThank you\e[0m :)
some
For ANSI escape sequences, 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.
paxdiablo
+1  A: 

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."
Patrick Cuff
+1  A: 

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.

chakrit
Would you be so kind to remember the escape sequences, just to give it a try?
BeowulfOF
umm... I'll try this evening.
chakrit
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.
paxdiablo
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.
paxdiablo
@Pax that's so ancient! havn't touched the config.nt in ages!
chakrit
+3  A: 

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.

aphoria
might as well add some more description about pslist
chakrit
You could just as easily use tasklist instead of pslist which uses a readily available program.
Joey
+10  A: 

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
paxdiablo
How do you get the backspace character (CTRL-H) into the script?
Patrick Cuff
Wow. I learnt something new today. +1
some
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...
BeowulfOF
@Patrick, I use gVim for my editing so CTRL-V, CRTL-H is the key sequence.
paxdiablo
@BeowulfOF, you can modify the payload to do whatever you want - the original installed Eclipse CDT and JDT on Windows.
paxdiablo
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 :-)
chakrit
A: 
A: 

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
A: 

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