Reputation: 31231
After coming across this issue twice I thought I would post it here to see if anyone knows how to get around it.
I can't seem to break out of nested loops using goto
, because it looks like when it breaks out of the inner loop, the parentheses then don't match because it never reached the inner closing one.
I have narrowed this down to a very simple example
for %%a in (1,2,3) do (
for %%b in (4,5,6) do (
echo Breaking
goto :BREAK
)
:BREAK
)
This results in the error
) was unexpected at this time.
I thought maybe adding extra brackets might solve the issue but it won't help unless I know I am going to break, if it's a conditional break, it's the same problem.
Is there any easy alternative to breaking out of the inner loop back into the outer, even when it is a conditional break using if
's and else
's?
Upvotes: 9
Views: 26970
Reputation: 2175
Welcome to DOS BAT with all its endemic glitches. Originally DOS BAT did not support nested loops _at _all; they still don't work exactly right. To avoid all the glitches and weirdnesses and inconsistencies, use Microsoft PowerShell instead.
You can avoid nested loops by restructuring your script to use internal subroutines invoked by 'call' statements. Make each 'for' do a single statement that calls a subroutine, rather than having the actions in an inline block.
A closely related issue is that breaking out of even a top level loop seems to not work fairly often. That's because the '( ... )' convenience construct was bolted on to the BAT language several years after the original, and doesn't play well with the rest of the language. In particular, _everything inside '( ... )' is substituted once and for all when the block is first entered. A construct like 'if %NOMORE%==yes ...' misbehaves because the value of %NOMORE% is substituted only once when the block is entered, _not as you'd expect every time it's executed. So if the value is changed later by anything either inside or outside the loop, the 'if' test will _not see the new value.
One way to get around this problem is by awkwardly restructuring your BAT file, either with a bunch of 'goto's so it does not use the '( ... )', or with a bunch of calls to otherwise-unneeded subroutines, or both-- this is what BAT wallahs did for many years before the syntactic convenience of a block was introduced (with its attendant odd behaviors). A better way to get around this problem is to use 'delayed expansion' (i.e. ' if !NOMORE!==yes ...' rather than 'if %NOMORE%==yes ...'). [Your version of Windows must support delayed expansion, and it must be enabled on your setlocal statement.]
HEAVILY EDITED because of new information and in response to a comment. (See the first comment for further information.)
Upvotes: 0
Reputation: 6958
DEFINED
can act evil for multiple reasons. I do not suggest using it unless you know exactly how it works and all potential use cases.
It is good practice to avoid using any delimiter characters (spaces, commas etc) in the variable name, for example IF DEFINED _variable will often fail if the variable name contains a delimiter character.
A safer answer that also does not use EXIT
or GOTO
:
@echo off
SETLOCAL ENABLEDELAYEDEXPANSION
Set time_to_exit=false
FOR /L %%O IN (1,1,3) DO (
echo [ Outer ] loop iteration %%O
FOR /L %%I IN (1,1,3) DO (
IF NOT "!time_to_exit!"=="true" (
echo ]Inner[ loop iteration %%I
IF "%%O"=="2" Set time_to_exit=true
)
)
)
output:
[ Outer ] loop iteration 1
]Inner[ loop iteration 1
]Inner[ loop iteration 2
]Inner[ loop iteration 3
[ Outer ] loop iteration 2
]Inner[ loop iteration 1
[ Outer ] loop iteration 3
Upvotes: 0
Reputation: 1
the proposal from Antonio is quite good. I used it in my example for parsing some CSV or HTML files out of a file list. (nested for loop). First look parses the file list, the second parses the content of each file. The separators are either ; or /.
@dir /O /B j:\temp\*.txt > j:\temp\filelist.doc
@for /F "tokens=1" %%a in (j:\temp\filelist.doc) DO (
@echo j:\temp\%%a
@set ef=
@for /F "delims=;/ tokens=1,2,3,4,5,6,7*" %%b in (j:\temp\%%a) DO @if not defined ef (
@if %%e == CNAME (
@echo Do something here
@set ef=yes
)
)
)
Upvotes: 0
Reputation: 67206
You may also omit the rest of iterations after the break with a controlling variable tied to the FOR:
@echo off
for %%a in (1,2,3) do (
echo Top-level loop: %%a
set break=
for %%b in (4,5,6) do if not defined break (
echo Nested loop: %%a-%%b
if %%b == 4 (
echo Breaking
set break=yes
)
)
)
This method preserve the code of the nested for-body in its original place and allows to easily set breaks at more than one point inside the for-body.
Antonio
Upvotes: 7
Reputation: 1557
Break by placing inner loop in a label.
for %%a in (1, 2, 3) DO (
call :innerloop
)
:innerloop
for %%b in (4, 5, 6) DO (
if %%b==<something> (
echo break
goto :break
)
)
:break
Upvotes: 15
Reputation: 1557
Could you provide more information about your requirement? in the meantime you can try this if it fits your requirement. keep me posted if you need more.
@echo off
for %%a in (1,2,3) do (
for %%b in (4,5,6) do (
echo Breaking
goto Break
)
)
:Break
Upvotes: -1