infinite loop with break condition

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
aGerman
Expert
Posts: 4678
Joined: 22 Jan 2010 18:01
Location: Germany

infinite loop with break condition

#1 Post by aGerman » 27 Dec 2011 17:35

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

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: infinite loop with break condition

#2 Post by dbenham » 27 Dec 2011 18:19

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

aGerman
Expert
Posts: 4678
Joined: 22 Jan 2010 18:01
Location: Germany

Re: infinite loop with break condition

#3 Post by aGerman » 27 Dec 2011 18:58

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

Squashman
Expert
Posts: 4484
Joined: 23 Dec 2011 13:59

Re: infinite loop with break condition

#4 Post by Squashman » 28 Dec 2011 08:48

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

Squashman
Expert
Posts: 4484
Joined: 23 Dec 2011 13:59

Re: infinite loop with break condition

#5 Post by Squashman » 28 Dec 2011 10:44

I was reading this over at stack overflow again.
http://stackoverflow.com/a/6730214

Would 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

aGerman
Expert
Posts: 4678
Joined: 22 Jan 2010 18:01
Location: Germany

Re: infinite loop with break condition

#6 Post by aGerman » 28 Dec 2011 11:07

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

Aacini
Expert
Posts: 1910
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

Re: infinite loop with break condition

#7 Post by Aacini » 28 Dec 2011 13:39

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).

aGerman
Expert
Posts: 4678
Joined: 22 Jan 2010 18:01
Location: Germany

Re: infinite loop with break condition

#8 Post by aGerman » 28 Dec 2011 13:52

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

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: infinite loop with break condition

#9 Post by dbenham » 28 Dec 2011 14:31

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

aGerman
Expert
Posts: 4678
Joined: 22 Jan 2010 18:01
Location: Germany

Re: infinite loop with break condition

#10 Post by aGerman » 28 Dec 2011 14:57

Thanks Dave :!: , that's it :D

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

Aacini
Expert
Posts: 1910
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

Re: infinite loop with break condition

#11 Post by Aacini » 28 Dec 2011 16:31

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

aGerman
Expert
Posts: 4678
Joined: 22 Jan 2010 18:01
Location: Germany

Re: infinite loop with break condition

#12 Post by aGerman » 28 Dec 2011 21:16

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

Aacini
Expert
Posts: 1910
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

Re: infinite loop with break condition

#13 Post by Aacini » 28 Dec 2011 22:02

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! :(

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: infinite loop with break condition

#14 Post by dbenham » 28 Dec 2011 22:32

I think you have discovered the problem, you just haven't figured out the solution. :wink:

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

aGerman
Expert
Posts: 4678
Joined: 22 Jan 2010 18:01
Location: Germany

Re: infinite loop with break condition

#15 Post by aGerman » 29 Dec 2011 08:59

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

Post Reply