Paul Smith
Paul Smith

Reputation: 3216

WHILE loop within FOR loop in batch

I've seen other questions that address parts of this problem, but I didn't see a solution that accomplished the whole thing. There's no such thing as "while" in the command processor of course, and since goto :line statements breaks out of all loops, it's not an option for iterating over some set of values for a specific duration before proceeding to the next value.

Here is pseudo-code for the logical flow I'm going for; the command processor has foiled my attempts to make it run so far. How would you construct this (besides ditching batch scripts and going to c# or something)?

Pseudocode:

SET %%durationMinutes=60
FOR %%X IN (10 20 40 80 160 200 0) DO (
  :: calculate elapsed minutes...
  WHILE %elapsedMinutes < %%durationMinutes DO (
    :: unrelated hocus pocus here, uses %%X as a variable
    call :foo %%X 
    // can't use goto to simulate the WHILE loop since it breaks %%X, so...?
  )
)

Upvotes: 3

Views: 8796

Answers (3)

Aacini
Aacini

Reputation: 67216

This problem have two faces. In first place, the fact that a goto break any nesting IF/FOR command, but perhaps more important is the fact that a While assembled with goto is very slow. One solution is to simulate a while with an endless loop: for /L %%i in () do ... and break it via a goto in a subroutine. The problem with this solution is that a for /L can NOT be broken with goto in the same cmd.exe context. So, the solution is to invoke a new cmd.exe just to execute the While.

The Batch file to execute in the new cmd.exe may be the same caller file, so we need control the execution of the While via special parameters in the same Batch file. Also, we may use auxiliary variables to make clearer all thus stuff. Here it is:

@echo off
setlocal EnableDelayedExpansion

rem While dispatcher
if "%1" equ "While" goto %2

rem Definition of auxiliary variables
set While=for /L %%a in () do if
set Do=(
set EndW=) else exit
set RunWhile=cmd /Q /C "%0" While

echo Example of While
echo/
goto RunMyWhile

rem Write the While code here
:MyWhile
set /A i=0, num=0
set /P "num=Enter number: "
%While% !num! gtr 0 %Do%
   set /A i+=1
   echo !i!- Number processed: !num!
   echo/
   set num=0
   set /P "num=Enter number: "
%EndW% !i!

rem Execute the While here
:RunMyWhile
%RunWhile% MyWhile
set i=%errorlevel%
echo/
echo While processed %i% elements

As you see, the While may return a numeric result to the caller code via ERRORLEVEL.

In your particular case:

SET durationMinutes=60
goto RunMyWhile

:MyWhile
:: calculate elapsed minutes...
%WHILE% !elapsedMinutes! < %durationMinutes% %DO%
    :: unrelated hocus pocus here, uses %3 as a variable
    call :foo %3
    :: calculate elapsed minutes again...
%EndW%

:RunMyWhile
FOR %%X IN (10 20 40 80 160 200 0) DO (
  %RunWhile% MyWhile %%X
)

This topic is explained with detail at this post

Upvotes: 5

dbenham
dbenham

Reputation: 130819

Harry has the right idea to use a subroutine in his answer. Normally the outer loop FOR variables are not accessible once the subroutine is called. But they "magically" become available again if the subroutine has its own FOR loop. This can eliminate the need to store the outer loop values in variables or pass the values as parameters.

@echo off
for %%x in (1 2 3 4 5) do (
  echo begin outer loop iteration, x=%%x
  call :innerLoop
  echo end of outer loop iteration, x=%%x
  echo(
)
echo Outer loop complete
exit /b

:innerLoop
echo inside subroutine, x FOR variable is inaccessible: x=%%x
for %%y in (1 2 3 4 5) do (
  if %%y gtr %%x goto :break
  echo within FOR loop inside subroutine: x=%%x y=%%y
)
:break
echo end of subroutine, x FOR variable is inaccessible: x=%%x
exit /b

Here are the results:

begin outer loop iteration, x=1
inside subroutine, x FOR variable is inaccessible: x=%x
within FOR loop inside subroutine: x=1 y=1
end of subroutine, x FOR variable is inaccessible: x=%x
end of outer loop iteration, x=1

begin outer loop iteration, x=2
inside subroutine, x FOR variable is inaccessible: x=%x
within FOR loop inside subroutine: x=2 y=1
within FOR loop inside subroutine: x=2 y=2
end of subroutine, x FOR variable is inaccessible: x=%x
end of outer loop iteration, x=2

begin outer loop iteration, x=3
inside subroutine, x FOR variable is inaccessible: x=%x
within FOR loop inside subroutine: x=3 y=1
within FOR loop inside subroutine: x=3 y=2
within FOR loop inside subroutine: x=3 y=3
end of subroutine, x FOR variable is inaccessible: x=%x
end of outer loop iteration, x=3

begin outer loop iteration, x=4
inside subroutine, x FOR variable is inaccessible: x=%x
within FOR loop inside subroutine: x=4 y=1
within FOR loop inside subroutine: x=4 y=2
within FOR loop inside subroutine: x=4 y=3
within FOR loop inside subroutine: x=4 y=4
end of subroutine, x FOR variable is inaccessible: x=%x
end of outer loop iteration, x=4

begin outer loop iteration, x=5
inside subroutine, x FOR variable is inaccessible: x=%x
within FOR loop inside subroutine: x=5 y=1
within FOR loop inside subroutine: x=5 y=2
within FOR loop inside subroutine: x=5 y=3
within FOR loop inside subroutine: x=5 y=4
within FOR loop inside subroutine: x=5 y=5
end of subroutine, x FOR variable is inaccessible: x=%x
end of outer loop iteration, x=5

Outer loop complete

Upvotes: 3

Harry Johnston
Harry Johnston

Reputation: 36308

Just put the contents of the loop in a subroutine. Here's pseudocode for a simple example:

for x in (1 2 3 4 5) {
  y = 1
  while (y <= x) {
    echo y
    y = y + 1
  }
  echo Looping
}

And the implementation in batch:

for %%x in (1 2 3 4 5) do set x=%%x & call :for_loop
goto :for_end
:for_loop

  set y=1

  :while_loop
  if not %y% LEQ %x% goto :while_end

    echo %y%
    set /a y=y+1

  goto :while_loop
  :while_end

  echo Looping

goto :eof
:for_end

And the output:

1
Looping
1
2
Looping
1
2
3
Looping
1
2
3
4
Looping
1
2
3
4
5
Looping

Upvotes: 1

Related Questions