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

DOS:
  1. set file=SomeAccounts.txt
  2. FOR /F %%i IN (%file%) DO (
  3. set username=%%i
  4. echo My account, %username%, starts with %username:~0,1%.
  5. )

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:

DOS:
  1. setlocal EnableDelayedExpansion
  2. set file=SomeAccounts.txt
  3. FOR /F %%i IN (%file%) DO (
  4. set username=%%i
  5. echo My account, !username!, starts with !username:~0,1!.
  6. )

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.

43 Responses to “Why That Batch For Loop Isn’t Working”

  1. ChiefWiggum Says:

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

  2. Pards Says:

    Many thanks for this little tip. I’ve been looking for a solution like this for a *really* long time. Very elegant.

  3. Phillip Says:

    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.

  4. Josh Says:

    Yeah, good point. If there was more to this batch script than just the single loop, that would definitely be a wise idea. Thanks.

  5. TGG Says:

    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

  6. Kerlutin Says:

    Awesome! Thanks!

  7. Beakie Hill Says:

    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

  8. PhilH Says:

    great stuff ,
    I struggled for 2 days before finding this

  9. PhilH Says:

    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?

  10. PhilH Says:

    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!

  11. Ketan Says:

    Thanks Joshua ,
    I started working on a batch script and was literally going mad with this for last 2 hrs.

    Thanks again :)

  12. runescape hack Says:

    @jake iam not sure thats really right grtz runescape hack

  13. dataman Says:

    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%

  14. mike Says:

    Works, thank you Josh!

  15. Herbie Says:

    Brilliant. Worked first time. No fuss. Many thanks.

  16. sAtancik Says:

    Works flawlessly! You saved my day. Many thanks Josh!

  17. A CMD Script Reads from a File -------- Some Notes in Microsoft Says:

    [...] 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 [...]

  18. painfree Says:

    Wow! That was exactly what I was looking for. This was driving me mad. Excellent work.

  19. GibStorm Says:

    I’ve been struggling with this problem what seems like forever. I cannot tell you how greatful I am! A million thanks.

  20. Hermund Says:

    I was banging my head in the wall until I found this article.

    Thank you very much!

  21. David Says:

    Thank you very much! This is very helpful!

  22. Eric Says:

    Wow! I was banging myself until I found your solution. Thanks Dude!!

  23. Vikas Says:

    This is really useful. I was searching for this exactly. Many thanks !!

  24. Neal Rosen Says:

    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

  25. HS Says:

    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 ‘!’

  26. Diana Says:

    I am new at window’s scripting and have a question.
    I have a file called temp_file that has the following 3 lines
    A
    B
    C

    FOR /f %%i in (%temp_file%) DO echo %%i

    It correctly shows
    A
    B
    C

    But when I try executing

    FOR /f %%i in (‘type %temp_file%’) DO (@call cycle.bat %%i)

    it only executes cycle.bat once and passes A as a parameter, instead of looping 3 times.
    Any help is appreciated.

  27. ben Says:

    thank you!

  28. Colin Firth Says:

    Dianna,

    I have attempted to recreate your problem but it has worked every time for me. What I would like to alert you of, is that you needn’t use ‘type’ . You can just use (filename.txt) and it will do the same thing. This may help in your situation.

  29. user Says:

    This post is very helpful!

  30. jayrod Says:

    This post was very helpful…
    i want to learn batch scripting so i volunteered to create a simple monitoring script here…
    i was stuck at this problem for three days.. :(
    i did use setLocal EnableDelayedExpansion, but still cannot echo the variable that i want to display..
    so the key was using “!” instead of “%” while inside the FOR loop.

    Thanks again! :)

  31. Sabsy Says:

    Thanks mate, your article was a lifesaver. This problem was doing my head in! :)

  32. Aj N Says:

    Thank you for this wonderful tip. For loop incremeters in Batch Script used to be the toughest challenge for me and I used to use a lot of GOTO labels in my scripts earlier. This solves my problem like a Charm!
    Thanks once again!

  33. Paul Graves Says:

    As might have been mentioned before – this is fantastic info! Thanks

  34. Colin Says:

    Perfect. I have struggled with this problem for ages and at last you have solved it for me.

  35. jeff Says:

    Thanks. I knew this was the problem, but I was struggling with solving it.

  36. Jason Says:

    Hi,

    I have the following code to check if a file exists in a folder. If the file is there is should exit, if it doesn’t is should run continueously. It exit every time after one run. Any ideas?

    for /f %%a IN (c:\jason\test.txt) do (Echo “File exists”
    exit
    )

  37. Josh Says:

    Jason – That’s not quite right. Here’s how you check the existence of a file:

    Stack Overflow: How to check if a file exists from inside a batch file

    Hope this helps.

  38. Mike Says:

    @Neal Rosen: The reason the ‘::’ comments don’t work is that is really just a cheat. The only way to comment out a line is using the ‘REM’.

    However, if any line starts with a ‘:’, then it is assumed to be a label and not actually processed. However, since labels aren’t allowed inside loops, you get the error when used there.

  39. Raj Says:

    I am looking to copy first 5 files (or 5 files modified by date)in the directory to another directory to do some encoding. I am struck with /F and /L options. Can you please help?

  40. Richard Says:

    Hello!

    I just want to do a simple counter within a for loop.
    This below does not seem to work. The variable count never gets incremented. The setlocal enabledelayedexpansion does not help either. Can someone help me?
    Thanks in advance!

    setlocal enabledelayedexpansion
    set count=0
    for /l %%i in (0,1,10) do (
    ::set /a count+=1
    echo %count% %%i
    )
    endlocal

  41. Soon Ann Says:

    Hi Richard

    I tried your script, replace %count% with !count! and it does display the variable count as incremented.

    Cheers.

  42. rahandra pramono Says:

    thanks for the tips

  43. DZ Says:

    Here is a less headache approach… No need for using delayed expansion or ! for variables.. works better too when there is multiple embedded for loops

    set count=0
    for /l %%i in (0,1,10 do(
    set var=%%i
    call :mainscript
    echo.
    )
    goto :EOF

    :mainscript
    set /a count+=1
    echo %count% %var%
    goto :EOF

    Just put your code outside of the for loop and call it within the loop.

  Theme Brought to you by Directory Journal and Elegant Directory.