Page 1 of 6
infinite loop with break condition
Posted: 27 Dec 2011 17:35
by aGerman
Hi everybody.
Sometimes I need an infinite loop that breaks if a specific value is reached. The only way I get it work is via GOTO.
Code: Select all
@prompt $g$s &setlocal &set /a n=0
:loop
set /a n+=1
echo #%n%
>nul ping -n 2 localhost
if %n%==10 goto exitloop
goto loop
:exitloop
pause
But the backward searching for the label is slow.
Another way to create an infinite loop is using FOR /L. That's much faster but I can't find a way to break it.
This doesn't work:
Code: Select all
@prompt $g$s &setlocal EnableDelayedExpansion &set /a n=0
for /l %%i in () do (
set /a n+=1
echo #!n!
>nul ping -n 2 localhost
if !n!==10 goto exitloop
)
:exitloop
pause
Is anybody out there who found a better way?
Regards
aGerman
Re: infinite loop with break condition
Posted: 27 Dec 2011 18:19
by dbenham
I've tried to do the same in the past, and never came up with a better solution.
I suppose if you are worried about disk thrashing while you wait for the exit condition to become true, you could try a hybrid approach.
Code: Select all
:loop
for /l %%N in (1 1 10000) do (
>nul ping -n 2 localhost
if ***exit condition is true*** goto exitLoop
)
goto loop
:exitLoop
Now the test is done almost entirely from memory. The :loop label is scanned only once every 10000 iterations. The delay is small once the exit condition is reached since the DO clause is ignored after executing the GOTO, and the FOR can count to 10000 very fast.
Dave Benham
Re: infinite loop with break condition
Posted: 27 Dec 2011 18:58
by aGerman
Hi Dave,
thanks for your reply.
I admit this ping example was senseless (only to show what happen if you try to exit the for /L loop).
Better example:
Some days ago in a German forum somebody came up with the question how to calculate the least common multiple of two numbers. There is a simple algorithm but it needs some kind of WHILE loop.
Code: Select all
@echo off &setlocal
call :lcm 3527 3784 var
echo %var%
pause
goto :eof
:lcm
setlocal
set /a "i=%1", "j=%2" 2>nul ||(endlocal &goto :eof)
:__lcm
if %j% neq 0 (
set /a k=j, j=i%%j, i=k
goto __lcm
)
set /a "j=%1*%2/i" 2>nul ||(endlocal &goto :eof)
endlocal &if "%~3" neq "" set /a "%~3=%j%"
goto :eof
Hence I'm basically looking for a WHILE equivalent. But you confirmed my fears that only GOTO is suitable to create a similar loop.
Regards
aGerman
Re: infinite loop with break condition
Posted: 28 Dec 2011 08:48
by Squashman
Was reading something over at Stack Overflow.
This creates your infinite loop with a break but is also kills your batch file. Which would also be pointless.
Code: Select all
@echo off & setlocal EnableDelayedExpansion
set /a num=0
for /L %%N in (1 0 10) do (
set /a num+=1
CALL :loop !num!
)
:__loop
()
:loop
IF "%~1"=="10" call :__loop 2>nul
Re: infinite loop with break condition
Posted: 28 Dec 2011 10:44
by Squashman
I was reading this over at stack overflow again.
http://stackoverflow.com/a/6730214Would this work for you.
Code: Select all
@echo off & setlocal EnableDelayedExpansion
if "%1"=="loop" (
set /a num=0
for /l %%f in (1 0 10) do (
set /a num+=1
IF "!num!"=="500" echo.>%%f.tmp
if exist %%f.tmp exit
)
goto :eof
)
cmd /v:on /q /d /c "%0 loop"
echo done
Re: infinite loop with break condition
Posted: 28 Dec 2011 11:07
by aGerman
Well, the first is not applicable. The second looks interesting, but the question is how to "rescue" the value of a variable?
Code: Select all
@echo off & setlocal EnableDelayedExpansion
if "%1"=="loop" (
set /a num=0
for /l %%f in (0) do (
set /a num+=1
echo !num!
IF "!num!"=="500" for /f %%g in ("!num!") do (endlocal & echo %%%% g=%%g & set "num=%%g" & exit)
)
)
call cmd /c "%~sf0 loop"
echo num=%num%
pause
Hmm...
Thanks anyway, perhaps somebody knows how to get it to work.
Regards
aGerman
Re: infinite loop with break condition
Posted: 28 Dec 2011 13:39
by Aacini
There is no way to break a FOR loop
in the same CMD context, so I used a trick to export the cycle to a new CMD that can be broken via an EXIT command, and the execution return to the caller code:
Code: Select all
@echo off
setlocal DisableDelayedExpansion
set LF=^
::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
echo %\n%
@echo off%\n%
set num=0%\n%
for /L %%%%i in (1,0,1) do (%\n%
set /A num+=1%\n%
echo !num!%\n%
if !num! == 10 (%\n%
call break%\n%
)%\n%
) > while.bat
echo exit > break.bat
echo Call the while:
cmd /c while
echo Return from while
In previous example I created the WHILE.BAT in the same caller program (using %\n% trick), but it can be a completely separated Batch file.
We may define a generic and more readable WHILE this way:
Code: Select all
echo %\n%
@echo off%\n%
set num=1%\n%
for /L %%%%i in (1,0,1) do (%\n%
call break %%1 %%2 %%3%\n%
echo !num!%\n%
set /A num+=1%\n%
) > while.bat
echo if not %%1 %%2 %%3 exit > break.bat
echo Call the while:
cmd /c while %%num%% leq 10
echo Return from while
However, the only way to get back a value from the cycle is via a disk file (I wonder if a macro could directly do this).
Re: infinite loop with break condition
Posted: 28 Dec 2011 13:52
by aGerman
Hi Aacini.
Thanks, I'm gonna test it.
Aacini wrote:However, the only way to get back a value from the cycle is via a disk file
You CAN get back a value. But only a single integer via errorlevel.
Code: Select all
@echo off & setlocal EnableDelayedExpansion
if "%1"=="loop" (
set /a num=0
for /l %%f in (0) do (
set /a num+=1
echo !num!
IF "!num!"=="100" for /f %%g in ("!num!") do (endlocal & echo %%%% g=%%g & exit %%g)
)
)
cmd /c "%~f0" loop
echo ERRORLEVEL=%errorlevel%
pause
Regards
aGerman
Re: infinite loop with break condition
Posted: 28 Dec 2011 14:31
by dbenham
aGerman wrote:Aacini wrote:However, the only way to get back a value from the cycle is via a disk file
You CAN get back a value. But only a single integer via errorlevel.
Or you can parse the output of your CMD statement using
FOR /F %%A IN ('CMD....') DO ....Dave Benham
Re: infinite loop with break condition
Posted: 28 Dec 2011 14:57
by aGerman
Thanks Dave
, that's it
Code: Select all
@echo off & setlocal EnableDelayedExpansion
if "%1"=="loop" (
set /a num=0
for /l %%f in (0) do (
set /a num+=1
IF "!num!"=="100" (echo(!num!&endlocal&exit)
)
)
for /f "delims=" %%i in ('cmd /c "%~f0" loop') do set "num=%%i"
echo num=%num%
pause
Regards
aGerman
Re: infinite loop with break condition
Posted: 28 Dec 2011 16:31
by Aacini
aGerman wrote:You CAN get back a value. But only a single integer via errorlevel.
Yes, it works!
Code: Select all
echo %\n%
@echo off%\n%
set num=0%\n%
set result=0%\n%
for /L %%%%i in (1,0,1) do (%\n%
set /A num+=1%\n%
if not %%1 %%2 %%3 call break !result!%\n%
echo !num!%\n%
set /A result+=num%\n%
) > while.bat
echo exit %%1 > break.bat
setlocal EnableDelayedExpansion
echo Call the while:
cmd /c while ^^^!num^^^! leq 10
echo Return from while: %ERRORLEVEL%
Code: Select all
Call the while:
1
2
3
4
5
6
7
8
9
10
Return from while: 55
Re: infinite loop with break condition
Posted: 28 Dec 2011 21:16
by aGerman
It's not necessary to create additional files.
Try:
Code: Select all
@echo off
if /i "%~1"=="while" call :while %*
cmd /c "%~f0" while num leq 10
echo Return from while: %ERRORLEVEL%
pause
goto :eof
:while
setlocal EnableDelayedExpansion
set num=0
set result=0
for /L %%i in (0) do (
set /A num+=1
if not !%~2! %~3 %~4 exit !result!
echo !num!
set /A result+=num
)
Regards
aGerman
Re: infinite loop with break condition
Posted: 28 Dec 2011 22:02
by Aacini
I need the help of a Batch macro expert
I tried to write a macro, called WHILE, to make good use of this trick in an easier way. I first wrote a subroutine that works ok. When I convert it to a macro I got an error in the line with the REM below:
Code: Select all
@echo off
setlocal DisableDelayedExpansion
set LF=^
::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
set while=for %%n in (1 2) do if %%n==2 (%\n%
call :StrLen argv argvLen=%\n%
set "body=!argv:*do=!"%\n%
call :StrLen body bodyLen=%\n%
set /A condLen=argvLen-bodyLen-2%\n%
for %%a in (!condLen!) do set "cond=!argv:~0,%%a!"%\n%
REM echo for /L %%%%w in (1,0,1) do if !cond! !body! else call whileBreak^> whileBody.bat%\n%
echo for /L %%%%w in (1,0,1 do if !cond! !body! else call whileBreak^> whileBody.bat%\n%
echo exit %%whileResult%%^> whileBreak.bat%\n%
cmd /Q /C whileBody%\n%
endlocal ^& set whileResult=^!errorlevel^!%\n%
) else setlocal EnableDelayedExpansion ^& set argv=
set !=^^^^!
setlocal EnableDelayedExpansion
set num=1
set whileResult=0
%while% %!%num%!% leq 10 do (%\n%
echo %!%num%!%%\n%
set /A whileResult+=num%\n%
set /A num+=1%\n%
)
echo While result: %whileResult%
goto :EOF
:StrLen string [result=[adjust]]
setlocal EnableDelayedExpansion
set str=%1
set str=!str:"= !
if "!str:~0,1!" == " " (
set "str=0%~1"
) else (
set "str=0!%1!"
)
set len=0
for /L %%A in (12,-1,0) do (
set /A "len|=1<<%%A"
for %%B in (!len!) do if "!str:~%%B,1!" == "" set /A "len&=~1<<%%A"
)
for %%v in (!len!) do endlocal&if not "%2" == "" (set /A "%2=%%v%3") else echo %%v
exit /B
The error is "do was unexpected at this time". I made several tests and discovered that the right parentheses of the "FOR ... IN (1,0,1)" is really closing
THE IF %%N==2 ( of the macro definition
The line below the FOR don't cause the error, but the whileBody is not completed correctly.
I can't discover the cause of this error. HELP ME, PLEASE!
Re: infinite loop with break condition
Posted: 28 Dec 2011 22:32
by dbenham
I think you have discovered the problem, you just haven't figured out the solution.
You just need to make sure the ) is escaped after the macro is defined. So you need to escape the escape when defining the macro.
I think this will get you past your immediate problem.
Code: Select all
echo for /L %%%%w in (1,0,1^^) do if !cond! !body! else call whileBreak^> whileBody.bat%\n%
I can't figure out how your %while% "call" works
Dave Benham
Re: infinite loop with break condition
Posted: 29 Dec 2011 08:59
by aGerman
I also can't find out how it should work.
I tried a different way (with finally 3 macros).
Syntax:
cmd /c "%~f0" label c1 c2 c3 RetVarName [VarName1 Value1 [VarNameN ValueN]]label - sub routine where the While loop is placed
c1 - variable name that should be compared
c2 - compare operator
c3 - value to compare with c1
RetVarName - variable name of the value that should be returned via errorlevel
VarName1 Value1 - variable name and value pairs for predefined variables used in the While loop
I'm stumbling upon a strange fault (see the REM line). Message: '"!_c2!" was unexpected at this time'
In this variable the compare operator will be saved, but not at the time when the macro variabe shall be assigned. In the next line I replaced
!_c2! with
lss and it works just fine.
It seems the cmd is parsing the syntax of IF even if it is not executed yet.
Is there a way to avoid that "early parsing" by any kind of escape sequence?Here's the code:
Code: Select all
@echo off &setlocal DisableDelayedExpansion
%$initWhile% :test
call :macros
:::::::::::::::::::::::::::::::::::::::::::::::::
cmd /c "%~fs0" test num lss 50 ret num 5 ret 6
echo %errorlevel%
pause
goto :eof
:test
%$While%
set /a num+=1
set /a ret+=num
echo !num! !ret!
%$Wend%
:macros
set LF=^
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"&rem TWO EMPTY LINES ABOVE REQUIRED!
set $initWhile=for /l %%I in (1 1 2) do if %%I==2 (%\n%
if defined _arg (%\n%
for /f %%J in ("!_arg!") do endlocal^&call %%J %%*%\n%
) else exit%\n%
) else setlocal EnableDelayedExpansion ^&set _arg=
set $While=(%\n%
setlocal EnableDelayedExpansion%\n%
call set "_args=%%*"%\n%
if "!_args!"=="" exit 1%\n%
for %%J in (!_args:* ^^=!) do if not defined _c1 (set "_c1=%%J") else (%\n%
if not defined _c2 (set "_c2=%%J") else (%\n%
if not defined _c3 (set "_c3=%%J") else (%\n%
if not defined _ret (set "_ret=%%J") else (%\n%
if not defined _var (set "_var=%%J") else (set "!_var!=%%J"^&set "_var=")%\n%
))))%\n%
for /l %%J in (0) do (%\n%
for /f "tokens=1,2" %%K in ("!_c1! !_ret!") do (%\n%
REM if not !%%K! !_c2! !_c3! exit !%%L!%\n%
if not !%%K! lss !_c3! exit !%%L!%\n%
)%\n%
set $Wend=))
goto :eof
Regards
aGerman