<http://www.netikka.net/tsneti/info/tscmd010.htm>
Copyright © 2003-2014 by Prof. Timo Salmi  
Last modified Mon 3-Feb-2014 12:30:41

 
Assorted NT/2000/XP/.. CMD.EXE Script Tricks
From the html version of the tscmd.zip 1cmdfaq.txt file
To the Description and the Index
 

This page is edited from the 1cmdfaq.txt faq-file contained in my tscmd.zip command line interface (CLI) collection. That zipped file has much additional material, including a number of detached .cmd script files. It is recommended that you also get the zipped version as a companion.

Please see "The Description and the Index page" for the conditions of usage and other such information.



10} How can I change the environment variable values within a FOR loop?

Unless enabledelayedexpansion is on, by default a for loop expands its environment variables before the execution of the loop. Unlike in programming with compilers the value of a variable that is changed within a loop will not be set to its new value until the loop is exited. (The same goes for any environment variable expression on multiple lines within parentheses.)

For more information type SET /?
Also see items #158 and #38. (Similar issues go for the statements within IF conditions.)

Consider the following example of a typical, but partly faulty attempt to count the number of lines in a file
  @echo off & setlocal enableextensions
  :: Make a test file
  for %%f in ("myfile.txt") do if exist %%f del %%f
  for %%f in (first second third fourth) do (
    echo This is the %%f line>>"myfile.txt")
  ::
  set lineCount=0
  ::
  for /f "delims=" %%r in ('type "myfile.txt"') do (
    set /a lineCount+=1
    echo %lineCount% %%r
    )
  echo The number of lines is %lineCount%
  ::
  :: Clean up
  for %%f in ("myfile.txt") do if exist %%f del %%f
  endlocal & goto :EOF

The output will obviously not be quite what one would want:
  D:\TEST>cmdfaq
  0 This is the first line
  0 This is the second line
  0 This is the third line
  0 This is the fourth line
  The number of lines is 4

There are ways to amend. One is to put the operations into a subroutine outside the loop as follows
  @echo off & setlocal enableextensions
  :: Make a test file
  for %%f in ("myfile.txt") do if exist %%f del %%f
  for %%f in (first second third fourth) do (
    echo This is the %%f line>>"myfile.txt")
  ::
  set lineCount=0
  ::
  for /f "delims=" %%r in ('type "myfile.txt"') do (
    call :WriteOneLine %%r
    )
  echo The number of lines is %lineCount%
  ::
  :: Clean up
  for %%f in ("myfile.txt") do if exist %%f del %%f
  endlocal & goto :EOF
  ::
  :: =============================================
  :WriteOneLine
  set /a lineCount+=1
  echo %lineCount% %*
  goto :EOF

The output will be
  D:\TEST>cmdfaq
  1 This is the first line
  2 This is the second line
  3 This is the third line
  4 This is the fourth line
  The number of lines is 4

The other option is to enable "delayed expansion"
  @echo off & setlocal enableextensions enabledelayedexpansion
  :: Make a test file
  for %%f in ("myfile.txt") do if exist %%f del %%f
  for %%f in (first second third fourth) do (
    echo This is the %%f line>>"myfile.txt")
  ::
  set lineCount=0
  ::
  for /f "delims=" %%r in ('type "myfile.txt"') do (
    set /a lineCount+=1
    echo !lineCount! %%r
    )
  echo The number of lines is %lineCount%
  ::
  :: Clean up
  for %%f in ("myfile.txt") do if exist %%f del %%f
  endlocal & goto :EOF
The value of an environment variable in delayed expansion in the above is to be indicated by !variable! instead of the more familiar %variable%. The output will be
  D:\TEST>cmdfaq
  1 This is the first line
  2 This is the second line
  3 This is the third line
  4 This is the fourth line
  The number of lines is 4

There is an interesting and sometimes useful twist using call. Consider (note, no delayed expansion needed)
  @echo off & setlocal enableextensions disabledelayedexpansion
  :: Make a test file
  for %%f in ("myfile.txt") do if exist %%f del %%f
  for %%f in (first second third fourth) do (
    echo This is the %%f line>>"myfile.txt")
  ::
  set lineCount=0
  ::
  for /f "delims=" %%r in ('type "myfile.txt"') do (
    set /a lineCount+=1
    call set line[%%lineCount%%]=%%r
    )
  echo The number of lines is %lineCount%
  for /L %%i in (1,1,%lineCount%) do call echo %%i %%line[%%i]%%
  ::
  :: Clean up
  for %%f in ("myfile.txt") do if exist %%f del %%f
  endlocal & goto :EOF

The output will be
  C:\_D\TEST>cmdfaq
  The number of lines is 4
  1 This is the first line
  2 This is the second line
  3 This is the third line
  4 This is the fourth line

Now, which one should one choose? Depends on the particulars. The advantage with the disabledelayedexpansion methods is that they are more tolerant of the poison characters &()[]{}^=;!'+,`~

Note, incidentally, that if there are empty lines in myfile.txt, those empty lines are ignored by the FOR /F loop. Also note the disappearance of the exclamation mark !. For example
  @echo off & setlocal enableextensions disabledelayedexpansion
  :: Make a test file
  for %%f in ("myfile.txt") do if exist %%f del %%f
  for %%f in (first "second &()[]{}^=;!'+,`~" third fourth) do (
    echo This is the %%f line>>"myfile.txt")
  echo.>>"myfile.txt"
  for %%f in (sixth seventh) do (
    echo This is the %%f line>>"myfile.txt")
  ::
  type "myfile.txt"
  pause
  ::
  setlocal enableextensions enabledelayedexpansion
  set lineCount=0
  ::
  for /f "delims=" %%r in ('type "myfile.txt"') do (
    set /a lineCount+=1
    echo !lineCount! %%r
    )
  echo The number of lines is %lineCount%
  ::
  :: Clean up
  for %%f in ("myfile.txt") do if exist %%f del %%f
  endlocal & goto :EOF

  C:\_D\TEST>cmdfaq
  This is the first line
  This is the "second &()[]{}^=;!'+,`~" line
  This is the third line
  This is the fourth line

  This is the sixth line
  This is the seventh line
  Press any key to continue . . .
  1 This is the first line
  2 This is the "second &()[]{}=;'+,`~" line
  3 This is the third line
  4 This is the fourth line
  5 This is the sixth line
  6 This is the seventh line
  The number of lines is 6

Consider the following task. I needed to output the sequence
            Item001.pdf
            Item002.pdf
            Item003.pdf
            Item004.pdf
            Item005.pdf
            Item006.pdf
            Item007.pdf
            :
            Item205.pdf
            Item206.pdf

The solution is e.g.
  @echo off & setlocal enableextensions enabledelayedexpansion
  for /L %%i in (1,1,206) do (
    set iii=000%%i
    set iii=!iii:~-3!
    echo             Item!iii!.pdf)
  endlocal & goto :EOF

Another example of a for loop, using this time the subroutine method
  @echo off
  ::
  :: A true life example of an XP script used in a repetitive task.
  :: It well demonstrates how change the values of environment
  :: variable WITHIN a FOR loop.
  ::
  echo.+-------------------------------------------------------------+
  echo ^| Convert Timo's IBM PC character text mail files into LATIN1 ^|
  echo ^| By Prof. Timo Salmi, Last modified Tue 25-November-2003     ^|
  echo +-------------------------------------------------------------+
  echo.
  ::
  :: Ask for confirmation before we go on, case insensitive
  set /p ask_=Convert, are you sure [y/N]?
  if /i not "%ask_%"=="y" if /i not "%ask_%"=="yes" goto :EOF
  :: It is prudent practice to release the query variable immediately
  :: (even if in this particular script it happens to be the only question)
  set ask_=
  ::
  :: Set the path for the origin and target files
  :: The names of the files is meant to stay the same
  set pathin_=C:\_D\MAIL
  set pathout_=C:\Documents and Settings\ts\mail
  ::
  :: Go trough all the relevant files
  :: and call the pseudo-subroutine for each one
  for %%f in (BATCH.MAI
  NEWS0001.MAI
  SCIWK001.MAI
  TIMO.MAI
  TIMO0001.MAI
  TIMO0002.MAI
  TIMO0003.MAI
  TIMO0004.MAI
  WORK0001.MAI
  WORK0002.MAI) do call :_subru %%f
  ::
  :: Finally clean up (instead of playing around with setlocal and endlocal)
  for %%v in (pathin_ pathout_ filein_ fileout_) do set %%v=
  :: Exit the script
  goto :EOF
  ::
  :: *** The pseudo-subroutine for each loop ***
  ::
  :_subru
  set filein_="%pathin_%\%1"
  set fileout_="%pathout_%\%1"
  ::
  :: Do the actual task
  :: note that this way we can easily have redirection within the loop
  :: IBM2LAT1 is an external (Turbo Pascal 7.01) filter program
  :: But that is beside the point since we could have any task here
  :: (Performs the same task as item #28)
  IBM2LAT1 < %filein_% > %fileout_%
  ::
  :: Show the relevant files at each loop, date/time size fullname
  for /f "tokens=1 delims=" %%f in (%filein_%) do echo %%~tf %%~zf %%~ff
  for /f "tokens=1 delims=" %%f in (%fileout_%) do echo %%~tf %%~zf %%~ff
  echo.
  goto :EOF

The next example is not about changing the values of the variables within a FOR loop, but it usefully demonstrates the tricks and syntax within a FOR loop. It also demonstrates using re-usable script function modules, more fully covered in Item #18. It is the familiar, common task of getting the current date and time components into environment variables. At the same time using a Visual Basic Script (VBScript) aided command line script for a portable solution is covered by the example.
  @echo off & setlocal enableextensions
  :: Assign a value for the temporary folder variable temp_
  call :AssignTemp temp_
  ::
  :: Get the current date and time elements into environment variables

  call :GetDateTime
  ::
  endlocal & goto :EOF
  ::
  :: Assign a temporary folder, use the default if not defined

  :AssignTemp
  setlocal
  set return_=%temp%
  if defined mytemp if exist "%mytemp%\" set return_=%mytemp%
  endlocal & set "%1=%return_%" & goto :EOF
  ::
  :GetDateTime
  set vbs_=%temp_%\getdatetimenow.vbs
  set skip=
  findstr "'%skip%VBS1" "%~f0" > "%vbs_%"
  for /f "tokens=1-6" %%a in ('
    cscript //nologo "%vbs_%"') do (
      set DD=%%a
      set MM=%%b
      set YYYY=%%c
      set HH=%%d
      set MI=%%e
      set SS=%%f)
  for %%f in ("%vbs_%") do if exist "%%~f" del "%%~f"
  :: Show the current time for debugging or demonstration
  setlocal enableextensions enabledelayedexpansion
  for %%a in (DD MM YYYY HH MI SS) do (echo %%a=!%%a!)
  endlocal
  goto :EOF
  '
  '........................................................................
  'A Visual Basic Script to get the current date and time
  '

  DateTimeNow=Now 'VBS1
  '
  Wscript.StdOut.Write Right(0 & Day(DateTimeNow), 2) & " " 'VBS1
  Wscript.StdOut.Write Right(0 & Month(DateTimeNow), 2) & " " 'VBS1
  Wscript.StdOut.Write Year(DateTimeNow) & " " 'VBS1
  '
  Wscript.StdOut.Write Right(0 & DatePart("h", DateTimeNow), 2) & " " 'VBS1
  Wscript.StdOut.Write Right(0 & DatePart("n", DateTimeNow), 2) & " " 'VBS1
  Wscript.StdOut.WriteLine Right(0 & DatePart("s", DateTimeNow), 2) & " " 'VBS1
The output could be e.g.
  C:\_D\TEST>cmdfaq
  DD=17
  MM=03
  YYYY=2012
  HH=20
  MI=55
  SS=21

References/Comments: (If a Google message link fails try the links within the brackets.)
  hh ntcmds.chm::/for.htm
  hh ntcmds.chm::/set.htm
  Google Groups Oct 14 2008, 4:15 am [M]
  Google Groups Nov 14 2010, 2:25 pm [M]