| Author |
Message |
|
aGerman
Expert
Joined: 22 Jan 2010 18:01 Posts: 1417 Location: Germany
|
 infinite loop with break condition
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: @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: @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
|
| 27 Dec 2011 17:35 |
|
 |
|
dbenham
Expert
Joined: 12 Feb 2011 21:02 Posts: 892 Location: United States (east coast)
|
 Re: infinite loop with break condition
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: :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
|
| 27 Dec 2011 18:19 |
|
 |
|
aGerman
Expert
Joined: 22 Jan 2010 18:01 Posts: 1417 Location: Germany
|
 Re: infinite loop with break condition
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: @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
|
| 27 Dec 2011 18:58 |
|
 |
|
Squashman
Joined: 23 Dec 2011 13:59 Posts: 1041
|
 Re: infinite loop with break condition
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: @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
|
| 28 Dec 2011 08:48 |
|
 |
|
Squashman
Joined: 23 Dec 2011 13:59 Posts: 1041
|
 Re: infinite loop with break condition
I was reading this over at stack overflow again. http://stackoverflow.com/a/6730214Would this work for you. Code: @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
|
| 28 Dec 2011 10:44 |
|
 |
|
aGerman
Expert
Joined: 22 Jan 2010 18:01 Posts: 1417 Location: Germany
|
 Re: infinite loop with break condition
Well, the first is not applicable. The second looks interesting, but the question is how to "rescue" the value of a variable? Code: @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
|
| 28 Dec 2011 11:07 |
|
 |
|
Aacini
Expert
Joined: 06 Dec 2011 22:15 Posts: 434 Location: México City, México
|
 Re: infinite loop with break condition
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: @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: 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).
|
| 28 Dec 2011 13:39 |
|
 |
|
aGerman
Expert
Joined: 22 Jan 2010 18:01 Posts: 1417 Location: Germany
|
 Re: infinite loop with break condition
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: @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
|
| 28 Dec 2011 13:52 |
|
 |
|
dbenham
Expert
Joined: 12 Feb 2011 21:02 Posts: 892 Location: United States (east coast)
|
 Re: infinite loop with break condition
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
|
| 28 Dec 2011 14:31 |
|
 |
|
aGerman
Expert
Joined: 22 Jan 2010 18:01 Posts: 1417 Location: Germany
|
 Re: infinite loop with break condition
Thanks Dave  , that's it Code: @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
|
| 28 Dec 2011 14:57 |
|
 |
|
Aacini
Expert
Joined: 06 Dec 2011 22:15 Posts: 434 Location: México City, México
|
 Re: infinite loop with break condition
aGerman wrote: You CAN get back a value. But only a single integer via errorlevel.
Yes, it works! Code: 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: Call the while: 1 2 3 4 5 6 7 8 9 10 Return from while: 55
|
| 28 Dec 2011 16:31 |
|
 |
|
aGerman
Expert
Joined: 22 Jan 2010 18:01 Posts: 1417 Location: Germany
|
 Re: infinite loop with break condition
It's not necessary to create additional files. Try: Code: @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
|
| 28 Dec 2011 21:16 |
|
 |
|
Aacini
Expert
Joined: 06 Dec 2011 22:15 Posts: 434 Location: México City, México
|
 Re: infinite loop with break condition
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: @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! 
|
| 28 Dec 2011 22:02 |
|
 |
|
dbenham
Expert
Joined: 12 Feb 2011 21:02 Posts: 892 Location: United States (east coast)
|
 Re: infinite loop with break condition
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: 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
|
| 28 Dec 2011 22:32 |
|
 |
|
aGerman
Expert
Joined: 22 Jan 2010 18:01 Posts: 1417 Location: Germany
|
 Re: infinite loop with break condition
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: @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
|
| 29 Dec 2011 08:59 |
|
|