38

Why does this batch file never break out of the loop?

For /L %%f In (1,1,1000000) Do @If Not Exist %%f Goto :EOF

Shouldn't the Goto :EOF break out of the loop?

Edit:

I guess I should've asked more explicitly... how can I break out of the loop?

Community
  • 1
  • 1
user541686
  • 205,094
  • 128
  • 528
  • 886

9 Answers9

11

Based on Tim's second edit and this page you could do this:

@echo off
if "%1"=="loop" (
  for /l %%f in (1,1,1000000) do (
    echo %%f
    if exist %%f exit
  )
  goto :eof
)
cmd /v:on /q /d /c "%0 loop"
echo done

This page suggests a way to use a goto inside a loop, it seems it does work, but it takes some time in a large loop. So internally it finishes the loop before the goto is executed.

Community
  • 1
  • 1
wimh
  • 15,072
  • 6
  • 47
  • 98
  • 9
    Don't use `exit` in batch files unless you actually want to close the current session. This isn't nice if you run the batch from the command prompt. – Joey Jul 18 '11 at 08:27
  • 1
    the batch file as a whole works perfectly from a command prompt. that's why I use `cmd /c` to call the part which does the exit. – wimh Jul 18 '11 at 08:29
  • 4
    Ah, didn't notice the `cmd` invocation there. Still, feels dirty. – Joey Jul 18 '11 at 08:34
  • 1
    @joey exit /b would exit just the batch file not the cmd window. so I suppose exit /b or a goto is fine – barlop Nov 26 '13 at 18:19
  • Could you explain what exactly this code is doing / how it is terminating the loop / under what condition? If it's just looping from 1 to 1000000 this isn't necessarily termination/break, it's just the end of execution – Collin Mar 18 '22 at 16:41
10

You could simply use echo on and you will see that goto :eof or even exit /b doesn't work as expected.

The code inside of the loop isn't executed anymore, but the loop is expanded for all numbers to the end.
That's why it's so slow.

The only way to exit a FOR /L loop seems to be the variant of exit like the example of Wimmel, but this isn't very fast nor useful to access any results from the loop.

This shows 10 expansions, but none of them will be executed

echo on
for /l %%n in (1,1,10) do (
  goto :eof
  echo %%n
)

Workaround

To loop (nearly) endless times, but with the possibility to use goto you can use nested loops.

set a=7
for /L %%n in (1,1,16) do ^
for /L %%n in (1,1,16) do ^
for /L %%n in (1,1,16) do ^
for /L %%n in (1,1,16) do ^
for /L %%n in (1,1,16) do ^
for /L %%n in (1,1,16) do ^
for /L %%n in (1,1,16) do ^
for /L %%n in (1,1,16) do (
   rem *** Do something 
   set /a "var=(var* 730005791) %% 20005693"
   if "!var!" == "14911847" goto :endLoop
)

:endLoop
jeb
  • 78,592
  • 17
  • 171
  • 225
  • 1
    This looks like the correct answer to the question. The goto has the intended end result, but the script interpreter is inefficient in an unexpected way. – Tydaeus Jun 09 '17 at 16:14
6

My answer
Use nested for loops to provide break points to the for /l loop.

for %%a in (0 1 2 3 4 5 6 7 8 9) do (
   for %%b in (0 1 2 3 4 5 6 7 8 9) do (
      for /l %%c in (1,1,10) do (
         if not exist %%a%%b%%c goto :continue
      )
   )
)
:continue

Explanation The code must be tweaked significantly to properly use the nested loops. For example, what is written will have leading zeros.
"Regular" for loops can be immediately broken out of with a simple goto command, where for /l loops cannot. This code's innermost for /l loop cannot be immediately broken, but an overall break point is present after every 10 iterations (as written). The innermost loop doesn't have to be 10 iterations -- you'll just have to account for the math properly if you choose to do 100 or 1000 or 2873 for that matter (if math even matters to the loop).

History I found this question while trying to figure out why a certain script was running slowly. It turns out I used multiple loops with a traditional loop structure:

set cnt=1
:loop
if "%somecriteria%"=="finished" goto :continue
rem do some things here
set /a cnt += 1
goto :loop

:continue
echo the loop ran %cnt% times

This script file had become somewhat long and it was being run from a network drive. This type of loop file was called maybe 20 times and each time it would loop 50-100 times. The script file was taking too long to run. I had the bright idea of attempting to convert it to a for /l loop. The number of needed iterations is unknown, but less than 10000. My first attempt was this:

setlocal enabledelayedexpansion
set cnt=1
for /l %%a in (1,1,10000) do (
   if "!somecriteria!"=="finished" goto :continue
   rem do some things here
   set /a cnt += 1
)

:continue
echo the loop ran %cnt% times

With echo on, I quickly found out that the for /l loop still did ... something ... without actually doing anything. It ran much faster, but still slower than I thought it could/should. Therefore I found this question and ended up with the nested loop idea presented above.

Side note It turns out that the for /l loop can be sped up quite a bit by simply making sure it doesn't have any output. I was able to do this for a noticeable speed increase:

setlocal enabledelayedexpansion
set cnt=1
@for /l %%a in (1,1,10000) do @(
   if "!somecriteria!"=="finished" goto :continue
   rem do some things here
   set /a cnt += 1
) > nul

:continue
echo the loop ran %cnt% times
avery_larry
  • 2,069
  • 1
  • 5
  • 17
5

you do not need a seperate batch file to exit a loop using exit /b if you are using call instead of goto like

call :loop

echo loop finished

goto :eof

:loop
FOR /L %%I IN (1,1,10) DO (
    echo %%I
    IF %%I==5 exit /b
)

in this case, the "exit /b" will exit the 'call' and continue from the line after 'call' So the output is this:

1
2
3
4
5
loop finished
  • 1
    You should try it with `for /L %%f in (1,1,10000000)` to see, that it doesn't solve the problem, Btw. `exit /b` and `goto :eof` are quite identical, the `exit /b` executes internally `goto :eof` – jeb Sep 24 '19 at 04:57
  • Works for me. In my case I can't use the goto statement because I want to return a value from the function and the goto moves to another function, so I used the solution above with the Exit /b. – IFink Mar 02 '20 at 16:26
5

It is impossible to get out of a FOR /L before it completes all iterations.

I have debugged the execution of a FOR /L by the cmd.exe process.

Microsoft could document it better and save us all this effort.

Facts:

  1. The loop is a simple while (TRUE) and the break only happens when the iteration limit is reached.
  2. When an EXIT /b or a GOTO is encountered, no more commands are executed until the end of the iterations.
  3. When an EXIT is encountered, the cmd.exe process is terminated.

Tests:

12 seconds

FOR /L %%G in (1,1,5000000) do (ECHO Only once & GOTO :EOF)

7 seconds

FOR /L %%G in (1,1,5000000) do (ECHO Only once & EXIT /b)

0 seconds, but this terminates the cmd.exe process

FOR /L %%G in (1,1,5000000) do (ECHO Only once & EXIT)
3

So I realize this is kind of old, but after much Googling, I couldn't find an answer I was happy with, so I came up with my own solution for breaking a FOR loop that immediately stops iteration, and thought I'd share it.

It requires the loop to be in a separate file, and exploits a bug in CMD error handling to immediately crash the batch processing of the loop file when redirecting the STDOUT of DIR to STDIN.

MainFile.cmd

ECHO Simple test demonstrating loop breaking.
ECHO.
CMD /C %~dp0\LOOP.cmd
ECHO.
ECHO After LOOP
PAUSE

LOOP.cmd

FOR /L %%A IN (1,1,10) DO (
    ECHO %%A
    IF %%A EQU 3 DIR >&0 2>NUL  )
)

When run, this produces the following output. You'll notice that both iteration and execution of the loop stops when %A = 3.

:>MainFile.cmd

:>ECHO Simple test demonstrating loop breaking.
Simple test demonstrating loop breaking.

:>ECHO.


:>CMD /C Z:\LOOP.cmd

:>FOR /L %A IN (1 1 10) DO (
ECHO %A
 IF %A EQU 3 DIR         1>&0 2>NUL
)

:>(
ECHO 1
 IF 1 EQU 3 DIR          1>&0 2>NUL
)
1

:>(
ECHO 2
 IF 2 EQU 3 DIR          1>&0 2>NUL
)
2

:>(
ECHO 3
 IF 3 EQU 3 DIR          1>&0 2>NUL
)
3

:>ECHO.


:>ECHO After LOOP
After LOOP

:>PAUSE
Press any key to continue . . .

If you need to preserve a single variable from the loop, have the loop ECHO the result of the variable, and use a FOR /F loop in the MainFile.cmd to parse the output of the LOOP.cmd file.

Example (using the same LOOP.cmd file as above):

MainFile.cmd

@ECHO OFF
ECHO.
ECHO Simple test demonstrating loop breaking.
ECHO.
FOR /F "delims=" %%L IN ('CMD /C %~dp0\LOOP.cmd') DO SET VARIABLE=%%L
ECHO After LOOP
ECHO.
ECHO %VARIABLE%
ECHO.
PAUSE

Output:

:>MainFile.cmd

Simple test demonstrating loop breaking.

After LOOP

3

Press any key to continue . . .

If you need to preserve multiple variables, you'll need to redirect them to temporary files as shown below.

MainFile.cmd

@ECHO OFF
ECHO.
ECHO Simple test demonstrating loop breaking.
ECHO.
CMD /C %~dp0\LOOP.cmd
ECHO After LOOP
ECHO.
SET /P VARIABLE1=<%TEMP%\1
SET /P VARIABLE2=<%TEMP%\2
ECHO %VARIABLE1%
ECHO %VARIABLE2%
ECHO.
PAUSE

LOOP.cmd

@ECHO OFF
FOR /L %%A IN (1,1,10) DO (
    IF %%A EQU 1 ECHO ONE >%TEMP%\1
    IF %%A EQU 2 ECHO TWO >%TEMP%\2
    IF %%A EQU 3 DIR >&0 2>NUL
)

Output:

:>MainFile.cmd

Simple test demonstrating loop breaking.

After LOOP

ONE
TWO

Press any key to continue . . .

I hope others find this useful for breaking loops that would otherwise take too long to exit due to continued iteration.

  • 3
    Upvoting although I feel like my eyes may be bleeding after seeing this now :-) thanks for posting this! – user541686 Dec 19 '16 at 09:00
  • I haven't tried this yet (I will shortly), but since the loop is in a separate batch file, wouldn't `IF %%A EQU 3 goto :EOF` also work instead of `IF %%A EQU 3 DIR >&0 2>NUL` ? I mean wouldn't `goto :EOF` immediately end execution of "LOOP.cmd" ? – Kevin Fegan Dec 28 '20 at 19:48
2

As jeb noted, the rest of the loop is skipped but evaluated, which makes the FOR solution too slow for this purpose. An alternative:

set F=1
:nextpart
if not exist "%F%" goto :EOF
echo %F%
set /a F=%F%+1
goto nextpart

You might need to use delayed expansion and call subroutines when using this in loops.

Community
  • 1
  • 1
Michel de Ruiter
  • 7,131
  • 5
  • 49
  • 74
  • Much simpler. Good job. – Okkenator Mar 19 '19 at 12:37
  • 1
    This is a good alternative in small script files. For large(ish) script files on a slow(ish) network this will be slow due to reading the entire file over and over and over and over again. Even so, it will still work well. – avery_larry Feb 25 '20 at 18:48
1

Assuming that the OP is invoking a batch file with cmd.exe, to properly break out of a for loop just goto a label;

Change this:

For /L %%f In (1,1,1000000) Do If Not Exist %%f Goto :EOF

To this:

For /L %%f In (1,1,1000000) Do If Not Exist %%f Goto:fileError
.. do something
.. then exit or do somethign else

:fileError
GOTO:EOF

Better still, add some error reporting:

set filename=
For /L %%f In (1,1,1000000) Do(
    set filename=%%f
    If Not Exist %%f set tempGoto:fileError
)
.. do something
.. then exit or do somethign else

:fileError
echo file does not exist '%filename%'
GOTO:EOF

I find this to be a helpful site about lesser known cmd.exe/DOS batch file functions and tricks: https://www.dostips.com/

The Coordinator
  • 13,007
  • 11
  • 44
  • 73
  • there are no`goto :eof`, `for /l` or code blocks like `()` in DOS. Those are Windows cmd features. They're completely different environments – phuclv Feb 06 '19 at 14:53
  • @phuclv II'm refering to this https://ss64.com/nt/for_l.html and also https://ss64.com/nt/goto.html. Can you expand on what you mean by ' Windows cmd features' since the OP is asking about a BAT file run by cmd,exe. – The Coordinator Feb 09 '19 at 12:43
  • You're saying "a for loop in DOS" and the OP isn't using DOS, and the commands above can't run in DOS either – phuclv Feb 09 '19 at 12:45
  • @phuclv Also not sure why you think there are not code blocks in DOS. I have many DOS batch files with FUNCTIONS complete also with codeblocks. see https://stackoverflow.com/a/4983https://www.dostips.com/DtTutoFunctions.php613/1584255 and for a more complete reference see – The Coordinator Feb 09 '19 at 12:46
  • The OP is using the exact DOS language and also tagged as batch-file defined as "A batch file is a text file containing a series of commands that are executed by the command interpreter on MS-DOS, IBM OS/2, or Microsoft Windows systems." That looks to me like a DOS batch file question.All the other answers are also answering as a DOS batch answer. – The Coordinator Feb 09 '19 at 12:48
  • because there's no code blocks or the commands I mentioned above in DOS. Just run MS-DOS in a VM or DOSBOX and see. [Here's the list of cmd.exe features that are not supported by command.com](https://stackoverflow.com/a/149918/995714). The OP isn't "using the exact DOS language" and didn't tag or mention DOS anywhere in the question. Indeed batch is a language that's used in OS/2, DOS and Windows but that doesn't mean all batch files are DOS batch files. [How do modern .bat files differ from old MS DOS .bat files?](https://retrocomputing.stackexchange.com/q/1891/1981) – phuclv Feb 09 '19 at 12:53
  • [Are the Command Prompt and MS-DOS the same thing?](https://superuser.com/q/451432/241386). And your link is wrong. Even https://www.dostips.com/DtTutoFunctions.php613/1584255 doesn't work – phuclv Feb 09 '19 at 12:53
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/188145/discussion-between-the-coordinator-and-phuclv). – The Coordinator Feb 09 '19 at 12:58
  • 1
    @phuclv You're being pedantic, which is not helpful. Perhaps on one of the English Language stacks, but not here. We all know there are minor differences between "MS Dos" (say, circa 1998), and modern Windows Command Prompt. But there is a history of Windows Command Prompt actually being MS Dos. So, some people use Windows Command Prompt and MS Dos, interchangeably. While they are not technically "interchangeable", we all know what someone means when they use the wrong one instead of the other. In cases where it might be important it's okay to mention it once, then let it go. Don't harp on it. – Kevin Fegan Dec 28 '20 at 19:40
-1

Did a little research on this, it appears that you are looping from 1 to 2147483647, in increments of 1.

(1, 1, 2147483647): The firs number is the starting number, the next number is the step, and the last number is the end number.

Edited To Add

It appears that the loop runs to completion regardless of any test conditions. I tested

FOR /L %%F IN (1, 1, 5) DO SET %%F=6

And it ran very quickly.

Second Edit

Since this is the only line in the batch file, you might try the EXIT command:

FOR /L %%F IN (1, 1, 2147483647) DO @IF NOT EXIST %%F EXIT

However, this will also close the windows cmd prompt window.

Gerhard
  • 22,678
  • 7
  • 27
  • 43
Tim
  • 28,212
  • 8
  • 63
  • 76
  • Yeah, but shouldn't `Goto :EOF` break out of the loop sooner or later? – user541686 Jul 18 '11 at 05:14
  • Depends on whether or not the IF NOT EXIST %%F condition is met or not. – Tim Jul 18 '11 at 05:15
  • I'm pretty sure I don't have *any* files with the names `1`, `2`, etc. in my folder... try it yourself if you think it's because of that. (BTW, I changed the numbers so people don't think that's the issue.) – user541686 Jul 18 '11 at 05:15
  • I agree that it should (I have tried it), but it appears to be failing the NOT EXIST check. I'm not sure why that is the case though. – Tim Jul 18 '11 at 05:25
  • It's actually failing the `Goto`, but I can't figure out why. (If you rename `Goto` to `Foo` it'll complain.) – user541686 Jul 18 '11 at 05:26
  • Can you post the complete batch file? Maybe there's something earlier in the file that is causing the problem. See my recent edit for what might be the problem. – Tim Jul 18 '11 at 05:27
  • That's literally the entire batch file! You can even change `%%` to `%` and then run it in the command prompt... same issue. – user541686 Jul 18 '11 at 05:32
  • The `set %%F=6` is nonsense. All you do is create variables named `1`, `2`, `3`, ... and set their value to `6`. And please don't use `exit` in a batch file unless your intent is to kill the session. That's not very nice for command-line use. – Joey Jul 18 '11 at 08:31
  • @Mehrdad I think the `goto` is working but all subsequent iterations are still "processed" in some way, which lasts for quite a while until 2147483647 is finally reached. – Michel de Ruiter Jan 28 '16 at 14:17