修改 for 循环以不在批处理脚本中使用延迟扩展

Posted

技术标签:

【中文标题】修改 for 循环以不在批处理脚本中使用延迟扩展【英文标题】:Modify for loop to not use delayedexpansion in batch script 【发布时间】:2017-12-10 17:26:03 【问题描述】:

在我努力理解for..do 循环语法及其对%% 变量的使用中。我已经经历了 2 个具体示例/实现,其中一个 for 循环不使用 DELAYEDEXPANSION,另一个使用 DELAYEDEXPANSION! 符号。第一个 for 循环似乎与 Windows XP 等旧操作系统兼容,而第二个 for 循环示例则不兼容。

具体来说,第一个 for 循环示例取自 answer(与 this 相关),第二个 for 循环示例是取自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

【问题讨论】:

延迟扩展功能在 Windows XP 下肯定可用。无论如何,为避免这种情况,请使用 call echo %%vara%%, %%varb%%call set day=%%vara:~0,2%% 之类的 call 语法,或者将循环体移动到 sub-routine 中,通过 call 调用它,将所有相关值作为 [arguments]( http:ss64.com/nt/syntax-args.html)并在那里使用普通的%-expansion;也参考concept of functions... 【参考方案1】:

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

这两个批处理文件也可以在 Windows 2000 和 Windows XP 上使用 cmd.exe 作为命令解释器。批处理文件不能在 MS-DOS、Windows 95 和 Windows 98 上使用非常有限的command.com 作为命令解释器。

可以在命令提示符窗口中使用参数/? 执行命令以获取该命令的帮助信息。当在帮助中编写启用的命令扩展时,这意味着仅在基于 Windows NT 的 Windows 版本上受 cmd.exe 支持,而使用 command.com 的 MS-DOS 或 Windows 9x 不支持。这意味着例如 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 并从命令提示符窗口中运行批处理文件来看到,因为现在可以看到哪些命令行分别是用@987654337 定义的整个命令块@ ... ) 真正被命令解释器预处理后执行。

因此,每当在命令块中定义或修改环境变量并且在同一命令块中引用其值时,就必须使用延迟扩展或使用变通方法。

一种解决方法如下所示:

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

一旦找到具有指定文件夹创建日期的感兴趣行,创建日期/时间将分配给环境变量,并使用命令 GOTOFOR 循环/strong> 继续在下面的标签上执行。 &运算符的含义见Single line with multiple commands using Windows batch file。

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

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

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

【讨论】:

很棒的答案。学到了很多。感谢您和@aschipfl 的帮助!

以上是关于修改 for 循环以不在批处理脚本中使用延迟扩展的主要内容,如果未能解决你的问题,请参考以下文章

批处理文件中延迟扩展的示例

如何从另一个 Windows 批处理脚本调用一个 Windows 批处理脚本并在两者中延迟扩展

在目标 c 中延迟或完成循环

批处理中如何在for循环中使用 find命令 ,尤其是引用相对路径

批处理中如何在for循环中使用 find命令 ,尤其是引用相对路径

IntersectionObserver 延迟脚本在浏览器控制台和 codePen 中工作,不在 WordPress 站点中