Why That Batch For Loop Isn’t Working
Time for another fun foray into Windows batch scripts. Perhaps you've used the FOR /F command to loop through the contents of a file (for instance, perhaps some data that was redirected to a text file from a command). Grab a line, act on its values, and output some text and commands.
Let's set this up. First, we have a data file named SomeAccounts.txt:
Josh
Mary
Suzy
Amanda
Trisha
Ben
Then, we have ProcessAccounts.bat, which we want to just loop through the accounts in the text file, tell us what they are, and tell us the first letter of the account name (just to have something to do):
Except when you do this, you encounter a problem: All of the values from the FOR loop are the same! It's as if the for loop ran the proper number of times, but it just ran on the last record over and over again! See below:
My account, Ben, starts with B.
My account, Ben, starts with B.
My account, Ben, starts with B.
My account, Ben, starts with B.
My account, Ben, starts with B.
My account, Ben, starts with B.
What's actually happening is the FOR loop is indeed running over every line, and setting the variables as instructed, but the results of those variables being altered isn't echoed until the FOR loop is complete, so the last value of the variable is what displays. This wouldn't be a problem if you were just using your FOR parameter, in this case %%i, but any variables you set while in the FOR loop, like username, experience this "wait until you're out of the loop" phenomenon.
The fix is simple enough, if you know about it! But I've found the solution to be a bit elusive, which is the whole point of sharing it now.
The key is the setlocal EnableDelayedExpansion command. As explained at ss64.com, making this statement before your FOR loop will enable you to display variables as their value at the moment you're referencing them, or their "intermediate values" while in the middle of the FOR loop. In addition to calling the setlocal command, you then have to reference your variables with the exclamation point (!) rather than percent (%) to indicate that you want to use the intermediate value.
Your script will then look like this:
It will now happily act as desired, outputting these results:
My account, Josh, starts with J.
My account, Mary, starts with M.
My account, Suzy, starts with S.
My account, Amanda, starts with A.
My account, Trisha, starts with T.
My account, Ben, starts with B.

January 7th, 2008 at 11:05 am
Thanks for EnableDelayedExpansion.
It was not practical to call cmd /v each time..
So I used to resort to multiple batch files along the lines of
FOR /F %%i IN (%file%) DO (
set username=%%i
@call processuser.bat %username%
)
EnableDelayedExpansion is much better !!
January 22nd, 2008 at 12:48 pm
Many thanks for this little tip. I’ve been looking for a solution like this for a *really* long time. Very elegant.
January 23rd, 2008 at 2:25 pm
Perfect explanation and example, previously could not figure out where I was going awry. One item to add, I believe in some scenarios you will want to close this with ENDLOCAL. Thanks again.
January 23rd, 2008 at 6:33 pm
Yeah, good point. If there was more to this batch script than just the single loop, that would definitely be a wise idea. Thanks.
August 28th, 2008 at 8:39 am
That solved a problem for me getting the loop to execute properly but then I wanted to put some IF blocks inside the FOR loop ( and/or vice-versa ) and whenever I try that the execution stops at the ELSE
:: @echo off
echo ————————————————–
echo +
echo checking for drive mapping NSPRD. Please wait…
echo +
echo ————————————————–
setlocal EnableDelayedExpansion
set OPT=
set username
set MP=H
set DR=dnr
FOR %%D in (1 2 ) DO (
echo !D!
IF “!D!” == “1″ (
set MP=H
set DR=dnr
set OPT=/user:%USERNAME%
) ELSE (
set MP=Q
set DR=clrc
)
IF EXIST %DR%: (
echo The drive is already mapped.
set MAP=1
) ELSE (
echo The drive was not previously mapped.
set MAP=0
IF
net use %MP%: \\carto-gis\%DR% %OPT%
)
)
PAUSE
REM pause to let the users – or me – see if the mappings worked before closing the command window
exit (0)
When I put the for loop inside an IF Block it won’t run at all.This works when the ‘IF’ and final ‘ )’ are removed or commented but not with them in
:: set tgg=0
:: if “%tgg%” == “0″ (
set OPT=” ”
FOR %%D in (1 2 3 4) DO CALL :L%%D
GOTO :LX
:L1
set MP=H
set DR=dnr
set OPT=/user:%USERNAME%
:: echo 1st
GOTO :LN
:L2
set MP=Q
set DR=datalib
:: echo 2nd
GOTO :LN
:L3
set MP=R
set DR=worklib
:: echo 3rd
GOTO :LN
:L4
set MP=S
set DR=esri
:: echo 4th
:LN
IF EXIST %MP%: (
echo The %MP% drive is already mapped.
set MAP=1
) ELSE (
echo The %MP% drive was not previously mapped.
echo It will be mapped now
set MAP=0
net use %MP%: \\carto-gis\%DR% %OPT%
:: )
:LX
:: PAUSE
Can anyone tell me where I am going wrong, or can’t this be done in a batch – maybe go to vbs
August 29th, 2008 at 9:49 am
Awesome! Thanks!
July 26th, 2009 at 10:27 pm
This post is solid gold! If I’d happened across it first I would have saved 3 days of frustration following other tips that didn’t pan out. The great thing about this one is it’s specific from first to last and doesn’t leave open syntax issues. Perfect for a learner like me. Ironic that Josh is almost apologetic about giving the routine something to do! Thank you, Josh
August 12th, 2009 at 12:01 pm
great stuff ,
I struggled for 2 days before finding this
August 12th, 2009 at 12:27 pm
hmm, not quite what I wanted
I want to increment a count variable within the loop
but that doesn’t work
is there another setlocal variable for this?
August 13th, 2009 at 10:31 am
hmm, not quite what I wanted
I want to increment a count variable within the loop
but that doesn’t work
is there another setlocal variable for this?
found it , variable has to be in ! eg !var!
September 12th, 2009 at 9:26 am
Thanks Joshua ,
I started working on a batch script and was literally going mad with this for last 2 hrs.
Thanks again
April 17th, 2010 at 9:31 am
@jake iam not sure thats really right grtz runescape hack
April 27th, 2010 at 4:04 am
Great tip.
Just ran into this problem. Thanks for the solution.
Some one asked how to increment variable value, I know it’s late, but here it is anyways.
SET VAR1=A, B, C, D
SET count=0
FOR %%j IN (%VAR1%) DO (
SET /A count+=1
ECHO !count! – %%j
)
ECHO %count%
June 28th, 2010 at 2:53 pm
Works, thank you Josh!
July 28th, 2010 at 3:25 am
Brilliant. Worked first time. No fuss. Many thanks.
August 12th, 2010 at 3:14 am
Works flawlessly! You saved my day. Many thanks Josh!
October 1st, 2010 at 7:55 am
[...] has really munged the MSI.There is one exception to the above, gratefully found reported here:http://blog.crankybit.com/why-that-batch-for-loop-isnt-working/It occurs when there is a separate variable to be set inside the FOR loop. In these cases, one [...]
November 4th, 2010 at 4:57 am
Wow! That was exactly what I was looking for. This was driving me mad. Excellent work.
January 27th, 2011 at 6:40 am
I’ve been struggling with this problem what seems like forever. I cannot tell you how greatful I am! A million thanks.
April 27th, 2011 at 9:50 am
I was banging my head in the wall until I found this article.
Thank you very much!
July 3rd, 2011 at 7:33 am
Thank you very much! This is very helpful!
July 28th, 2011 at 1:02 pm
Wow! I was banging myself until I found your solution. Thanks Dude!!
August 4th, 2011 at 8:51 am
This is really useful. I was searching for this exactly. Many thanks !!
October 2nd, 2011 at 3:05 pm
Hi All,
ok so just curious…
This does not work:
:: Hi There
@echo off
for /L %%a IN (1,1,10) do (
echo hi there
:: COMMENT
)
exit /b
But this DOES:
:: Hi There
@echo off
for /L %%a IN (1,1,10) do (
echo hi there
REM COMMENT
)
exit /b
POR QWA???
Cheerz,
Neal
October 26th, 2011 at 3:23 pm
Thanks! It helped me to find out that the variable that depends on the parameter of the for loop has to be written within between two ‘!’