Search code examples
windowsbatch-filewindows-xpdelayedvariableexpansion

Modify for loop to not use delayedexpansion in batch script


In my efforts to understand the for..do loops syntax and their use of %% variables. I have gone through 2 specific examples/implementations where the one for loop does not use DELAYEDEXPANSION and another where it does use DELAYEDEXPANSION with the ! notation. The 1st for loop appears to be compatible with older OSs like the Windows XP whereas the 2nd for loop example does not.

Specifically, the 1st for loop example is taken from this answer (which is related to this) and the 2nd for loop example is taken from this answer.

Modified code for both examples copied below:

1st for loop

for /f "tokens=2 delims==" %%a in ('wmic OS Get localdatetime /value') do set "dt=%%a"
set "YY=%dt:~2,2%"
set "YYYY=%dt:~0,4%"
set "MM=%dt:~4,2%"
set "DD=%dt:~6,2%"
set "HH=%dt:~8,2%"
set "Min=%dt:~10,2%"
set "Sec=%dt:~12,2%"
set "datestamp=%YYYY%%MM%%DD%"
set "timestamp=%HH%%Min%%Sec%"
echo datestamp: "%datestamp%"
echo timestamp: "%timestamp%"

2nd for loop

SETLOCAL ENABLEDELAYEDEXPANSION
set "path_of_folder=C:\folderA\folderB"

for /f "skip=5 tokens=1,2,4 delims= " %%a in (
 'dir /ad /tc "%path_of_folder%\."') do IF "%%c"=="." (
  set "dt=%%a"
  set vara=%%a
  set varb=%%b
  echo !vara!, !varb!
  set day=!vara:~0,2!
  echo !day!
)

Since I have been reading and seeing issues where delayed expansion (or the ! notation) is not compatible with older OSs (e.g. Windows XP), I would like to see how to write the 2nd loop like the 1st loop; i.e. without the use of DELAYEDEXPANSION.


Solution

  • I explain in detail what aschipfl wrote already absolutely right in his comment.

    Both batch files work also on Windows 2000 and Windows XP using also cmd.exe as command processor. The batch files do not work on MS-DOS, Windows 95 and Windows 98 using very limited command.com as command interpreter.

    A command can be executed with parameter /? in a command prompt window to get output the help for this command. When in help is written with enabled command extensions it means supported only by cmd.exe on Windows NT based Windows versions and not supported by MS-DOS or Windows 9x using command.com. That means, for example, for /F or if /I or call :Subroutine are not available on Windows 9x, or on Windows NT based Windows with command extensions explicitly disabled. On Windows 9x it is not even possible to use "%~1" or "%~nx1".

    The first batch file executes in FOR loop only one command exactly once:

    set "dt=%%a"
    

    That command line requires already enabled command extensions. All other commands below are executed after the FOR loop finished. In other words the FOR loop in first batch file does not use a command block to run multiple commands within the FOR loop.

    Whenever the Windows command processor detects the beginning of a command block on a command line, it processes the entire command block before executing the command on this command line the first time.

    This means for second batch file all variable references using %Variable% are expanded already before the command FOR is executed and then the commands in the command block are executed with the values of the variables as defined above FOR command line. This can be seen by removing @echo off from first line of batch file or change it to @echo ON and run the batch file from within a command prompt window because now it can be seen which command lines respectively entire command blocks defined with ( ... ) are really executed after preprocessing them by the Windows command processor.

    So whenever an environment variable is defined or modified within a command block and its value is referenced in same command block it is necessary to use delayed expansion or use workarounds.

    One workaround is demonstrated below:

    setlocal EnableExtensions DisableDelayedExpansion
    set "FolderPath=%SystemRoot%\System32"
    
    for /F "skip=5 tokens=1,2,4 delims= " %%a in ('dir /AD /TC "%FolderPath%\."') do if "%%c"=="." (
        set "VarA=%%a"
        set "VarB=%%b"
        call echo %%VarA%%, %%VarB%%
        call set "Day=%%VarA:~0,2%%
        call echo %%Day%%
    )
    
    endlocal
    pause
    

    As there is no @echo off at top of this batch code it can be seen on executing the batch file what happens here. Each %% is modified on processing the command block to just %. So executed are the command lines.

    call echo %VarA%, %VarB%
    call set "Day=%VarA:~0,2%
    call echo %Day%
    

    The command CALL is used to process the rest of the line a second time to run the ECHO and the SET commands with environment variable references replaced by their corresponding values without or with string substitution.

    The disadvantage of this solution is that CALL is designed primary for calling a batch file from within a batch file. For that reason the command lines above result in searching first in current directory and next in all directories of environment variable PATH for a file with name echo respectively set with a file extension of environment variable PATHEXT. That file searching behavior causes lots of file system accesses, especially on running those command lines in a FOR loop. If there is really found an executable or script file with file name echo or set, the executable respectively the script interpreter of the script file would be executed instead of the internal command of cmd.exe as usually done on using such a command line. So this solution is inefficient and not fail-safe on execution of the batch file.

    Another workaround to avoid delayed expansion is using a subroutine:

    @echo off
    setlocal EnableExtensions DisableDelayedExpansion
    set "FolderPath=%SystemRoot%\System32"
    
    for /F "skip=5 tokens=1,2,4 delims= " %%a in ('dir /AD /TC "%FolderPath%\."') do if "%%c"=="." call :ProcessCreationDate "%%a" "%%b"
    
    endlocal
    pause
    exit /B
    
    :ProcessCreationDate
    echo %~1, %~2
    set "Day=%~1"
    set "Day=%Day:~0,2%
    echo %Day%
    goto :EOF
    

    A subroutine is like another batch file embedded in current batch file.

    The command line with exit /B avoids a fall through to the code of the subroutine.

    The command line with goto :EOF would not be necessary if the line above is the last line of the batch file. But it is recommended to use it nevertheless in case of more command lines are ever added later below like a second subroutine.

    The second batch file is for getting the day on which the specified folder was created. It would be possible to code this batch file without usage of delayed expansion and any workarounds.

    @echo off
    setlocal EnableExtensions DisableDelayedExpansion
    set "FolderPath=%SystemRoot%\System32"
    
    for /F "skip=5 tokens=1,2,4 delims= " %%a in ('dir /ad /tc "%FolderPath%\." 2^>nul') do if "%%c"=="." set "CreationDate=%%a, %%b" & goto OutputDateAndDay
    
    echo Failed to get creation date of "%FolderPath%"
    endlocal
    pause
    exit /B
    
    :OutputDateAndDay
    echo %CreationDate%
    set "Day=%CreationDate:~0,2%
    echo %Day%
    endlocal
    pause
    

    Once the line of interest with the creation date of specified folder is found, the creation date/time is assigned to an environment variable and the FOR loop is exited with using command GOTO to continue execution on a label below. For the meaning of operator & see single line with multiple commands using Windows batch file.

    This solution is better than all other methods because the FOR loop executes the single command line with the three commands IF, SET and GOTO only once which makes this solution the fastest. And it outputs an error message when it was not possible to determine the creation date of the directory because of the directory does not exist at all.

    Of course it would be possible to add a GOTO command also on the other solutions to exit FOR loop once the creation date of the directory was determined and output. The last solution is nevertheless the fastest and in my point of view best one for this task.

    BTW: All posted batch file examples were tested on Windows XP and produced the expected output.