修改 for 循环以在批处理脚本中不使用 delayedexpansion

Modify for loop to not use delayedexpansion in batch script

我努力理解 for..do 循环语法及其对 %% 变量的使用。我经历了 2 个特定的 examples/implementations,其中一个 for 循环不使用 DELAYEDEXPANSION,另一个使用 DELAYEDEXPANSION! 表示法。第一个 for 循环似乎与 Windows XP 等较旧的操作系统兼容,而第二个 for 循环示例则不兼容。

具体来说,第一个 for 循环 示例取自 (which is related to ) and the 2nd for loop example is taken from this answer

下面复制的两个示例的修改代码:

第一个 for 循环

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%"

第二个for循环

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

由于我一直在阅读并看到延迟扩展(或 ! 符号)与旧操作系统(例如 Windows XP)不兼容的问题,我希望看到 如何像第一个循环一样编写第二个循环;即不使用 DELAYEDEXPANSION

我详细解释一下 aschipfl 在他的评论中写的已经完全正确了。

这两个批处理文件也适用于 Windows 2000 和 Windows XP,还使用 ​​cmd.exe 作为命令解释器。批处理文件不适用于 MS-DOS,Windows 95 和 Windows 98 使用非常有限的 command.com 作为命令解释器。

可以在命令提示符 window 中使用参数 /? 执行命令,以输出该命令的帮助。当在帮助中写入 with enabled command extensions 时,这意味着仅 cmd.exe 在 Windows NT based Windows 版本上支持,MS-DOS 不支持或 Windows 9x 使用 command.com。这意味着例如 for /Fif /Icall :Subroutine 在 Windows 9x 上不可用,或者在 Windows 基于 NT 的 Windows 上具有显式的命令扩展禁用。在 Windows 9x 上甚至不可能使用 "%~1""%~nx1".

第一个批处理文件在FOR中执行,只循环1条命令恰好1次:

set "dt=%%a"

下面的所有其他命令在FOR 循环完成后执行。换句话说,第一个批处理文件中的 FOR 循环不使用命令块来 运行 FOR 循环中的多个命令。

每当 Windows NT 命令解释器检测到命令行上命令块的开头时,它会在第一次执行该命令行上的命令之前处理整个命令块。

这意味着对于第二个批处理文件,所有使用 %Variable% 的变量引用都在执行命令 FOR 之前展开,然后执行命令块中的命令上面定义的变量值 FOR 命令行。这可以通过从批处理文件的第一行删除 @echo off 或将其更改为 @echo ON 和 运行 命令提示符 window 中的批处理文件来看到,因为现在它可以看到哪些命令行分别用 ( ... ) 定义的整个命令块在命令解释器预处理后真正执行。

因此,无论何时在命令块中定义或修改环境变量,并且在同一命令块中引用其值,都需要使用延迟扩展或使用变通方法。

下面演示了一种解决方法:

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

由于此批处理代码顶部没有 @echo off,因此可以在执行批处理文件时看到此处发生的情况。每个 %% 在处理命令块时被修改为 %。所以执行的是命令行。

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

命令 CALL 用于将行的其余部分第二次处理为 运行 ECHOSET 命令将环境变量引用替换为其对应的值,不带或带字符串替换。

另一种避免延迟扩展的解决方法是使用子例程:

@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
goto :EOF

:ProcessCreationDate
echo %~1, %~2
set "Day=%~1"
set "Day=%Day:~0,2%
echo %Day%
goto :EOF

子例程就像嵌入在当前批处理文件中的另一个批处理文件。

第一个 goto :EOF 避免跳转到子例程的代码。

如果上面的行是批处理文件的最后一行,则不需要第二个 goto :EOF。但是还是建议使用它,以防以后像第二个子例程一样添加更多命令行。

第二个批处理文件用于获取创建指定文件夹的日期。可以在不使用延迟扩展和任何变通方法的情况下编写此批处理文件。

@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
goto :EOF

:OutputDateAndDay
echo %CreationDate%
set "Day=%CreationDate:~0,2%
echo %Day%
endlocal
pause

一旦找到具有指定文件夹创建日期的兴趣行,将创建 date/time 分配给环境变量并退出 FOR 循环使用命令 GOTO 继续执行下面的标签。有关 & 运算符的含义,请参阅 Single line with multiple commands using Windows batch file.

此解决方案优于所有其他方法,因为 FOR 循环使用 3 个命令 IFSETGOTO 仅一次,这使得该解决方案最快。当由于目录根本不存在而无法确定目录的创建日期时,它会输出错误消息。

当然也可以在其他解决方案上添加 GOTO 命令以在创建日期后退出 FOR 循环目录已确定并输出。最后一个解决方案仍然是最快的,并且在我看来是完成此任务的最佳解决方案。

顺便说一句:所有发布的批处理文件示例都在 Windows XP 上进行了测试并产生了预期的输出。