批处理:如何在嵌套的 for 循环中设置变量并在其外部重新使用它(enabledelayedexpansion 不起作用)

Batch: How to set variable in a nested for-loop and re-use it outside of it (enabledelayedexpansion does not work)

我在一个文件夹中有一个批处理,其目标是执行位于其子文件夹中的所有批处理文件并评估它们的 errorlevel。如果其中至少一个等于 1,主批将 return 一个等于 1 的退出代码。这并不意味着主批必须在第一个错误级别等于 1 时退出:所有子 -无论如何都必须执行批处理文件。

编辑:所有子批处理文件 return 如果失败则退出代码等于 1,如果通过则退出代码等于 0(它们都是我自己编写的用于测试目的的批处理文件)。

问题:exit_code 变量在循环外永远不会改变。

我在 SO 上发现了类似的问题(还有一个非常相似的问题:Counting in a FOR loop using Windows Batch script)但它们没有帮助(可能我不明白......我不知道,我是批处理脚本的新手)。

提前致谢。

代码:

@echo off
setlocal enabledelayedexpansion
set exit_code=0
for /D %%s in (.\*) do (
    echo ********************  %%~ns  ********************
    echo.
    cd %%s
    for %%f in (.\*.bat) do (
        echo calling %%f
        call %%f
        if errorlevel 1 (
            set exit_code=1
        )
    )
    echo.
    echo.
    cd ..   
)

echo !exit_code!

exit /B !exit_code!

让我们假设启动主批处理文件的当前目录是 C:\Temp\Test,其中包含以下文件夹和文件:

  • 开发与测试
    • 发展 & Test.bat
  • 你好世界!
    • Hello World!.bat
  • 版本信息
    • VersionInfo.bat
  • Main.bat

批处理文件 Development & Test.bat 仅包含以下行:

@dir ..\Development & Test

批处理文件 Hello World!.bat 仅包含以下行:

@echo Hello World!

批处理文件 VersionInfo.bat 仅包含以下行:

@ver

批处理文件 main.bat 包含以下行:

@echo off
setlocal EnableExtensions DisableDelayedExpansion
cls
set "exit_code=0"
for /D %%I in ("%~dp0*") do (
    echo ********************  %%~nxI  ********************
    echo/
    for %%J in ("%%I\*.bat") do (
        echo Calling %%J
        echo/
        pushd "%%I"
        call "%%J"
        if errorlevel 1 set "exit_code=1"
        popd
    )
    echo/
    echo/
)
echo Exit code is: %exit_code%
endlocal & exit /B %exit_code%

一个command prompt被打开,接下来手动依次执行以下命令行:

C:\Temp\Test\Main.bat
echo Errorlevel is: %errorlevel%
ren "C:\Temp\Test\Development & Test\Development & Test.bat" "Development & Test.cmd"
C:\Temp\Test\Main.bat
echo Errorlevel is: %errorlevel%

第一次执行 Main.bat 导致退出,值为 1,如在命令提示符 window 中看到的那样:

Errorlevel is: 1

原因是 dir 命令编码错误,目录名称未包含在双引号中,导致将 Test 解释为要执行的命令。因此,dir 命令行导致以下错误输出:

 Volume in drive C is TEMP
 Volume Serial Number is 14F0-265D

 Directory of C:\Temp\Test

File Not Found
'Test' is not recognized as an internal or external command,
operable program or batch file.

这个批处理文件的退出代码是 not 0 由于错误,因此条件 if errorlevel 1 为真且 set "exit_code=1" 已经在第一个执行的批处理文件上执行了。

其他两个批处理文件的处理总是以 0 作为退出代码结束。

命令ren用于更改Development & Test.bat的文件扩展名,使批处理文件的下一个名称为Development & Test.cmd,导致main.bat忽略它。 Main.bat 的第二次执行导致退出并显示 0,如在线所示:

Errorlevel is: 0

请阅读以下页面了解所有代码更改的原因:

  • DosTips 论坛主题:ECHO. FAILS to give text or blank line - Instead use ECHO/
  • Why is no string output with 'echo %var%' after using 'set var = text' on command line?
  • How does the Windows Command Interpreter (CMD.EXE) parse scripts?
  • Single line with multiple commands using Windows batch file

变更摘要:

  • Delayed expansionMain.bat 中未启用,因为此处不需要处理包含感叹号(如 C:\Temp\Test\Hello World!\Hello World!.bat.
  • 的正确目录和文件名
  • IJ 被用作循环变量而不是 sf 因为后两个字母在某些情况下可能被误解为循环变量修饰符.因此,在引用循环变量时,最好避免使用对命令有特殊含义的字母 for
  • 使用
  • %~dp0 而不是 .\ 以确保批处理文件在批处理文件的目录中搜索 non-hidden 子目录,而与启动时的当前目录无关批处理文件。此表达式引用参数 0 的驱动器和路径,这是当前执行的批处理文件 Main.bat 的完整路径。批处理文件的引用路径总是以反斜杠结尾,因此 %~dp0* 连接,没有额外的反斜杠。
  • 目录和文件名参数用双引号括起来,也适用于包含 space 或其中一个字符 &()[]{}^=;!'+,`~ 的名称。 %%~nxI%%J 两个 echo 命令行中的 %%~nxI%%J 没有用双引号括起来,只要不启用延迟扩展就不需要。批处理文件确保 Main.bat.
  • 不是这种情况
  • 在第一个 FOR 循环中使用 "%~dp0*" 而不是 .\* 导致将目录名称分配给循环变量 I完整路径永远不会以反斜杠结尾。 "%%I\*.bat" 的用法确保将 non-hidden 批处理文件的完整限定文件名分配给循环变量 J。通常最好尽可能使用完全限定的 directory/file 名称。这也经常有助于解决错误。
  • 这两个 cd 命令被 pushdpopd 替换并移动到内部 FOR 循环中。然后,被调用的批处理文件是否仅适用于作为被调用批处理文件目录的当前目录或独立于当前目录(如 Main.bat)工作都没有关系。此外,如果调用的批处理文件更改当前目录不再重要,因为 popd 启动时的初始当前目录 Main.bat 恢复为当前目录,这可能是文件存储的目录由调用的批处理文件处理。 pushdpopd 的使用使该批处理文件也可以存储在网络资源上,并且 Main.bat 以其 UNC 路径启动。

最重要的修改在最后一行:

endlocal & exit /B %exit_code%

此命令行首先由 Windows 命令处理器 cmd.exe 解析,并将 %exit_code% 替换为在本地环境设置中定义的环境变量 exit_code 的当前值 setlocal EnableExtensions DisableDelayedExpansion。所以命令行变成

endlocal & exit /B 0

endlocal & exit /B 1

然后 Windows 命令处理器执行命令 endlocal 来恢复先前在 Main.bat 之外定义的环境,如果在初始执行环境中未定义,则导致 exit_code 不再定义.然后执行带有选项 /B 的命令 exit 退出批处理文件 Main.bat 的处理,并返回 01 到父进程 cmd.exe 将退出代码分配给变量 errorlevel.

嗯,Main.bat的批处理文件代码还有一个问题。被调用的批处理文件可以修改 Main.bat 的环境变量 exit_code 的值,使用相同的环境变量而无需使用命令 setlocal 定义本地环境。解决方案是在调用批处理文件之前和之后额外使用命令 setlocalendlocal

@echo off
setlocal EnableExtensions DisableDelayedExpansion
cls
set "exit_code=0"
for /D %%I in ("%~dp0*") do (
    echo ********************  %%~nxI  ********************
    echo/
    for %%J in ("%%I\*.bat") do (
        echo Calling %%J
        echo/
        pushd "%%I"
        setlocal
        call "%%J"
        endlocal
        if errorlevel 1 set "exit_code=1"
        popd
    )
    echo/
    echo/
)
echo Exit code is: %exit_code%
endlocal & exit /B %exit_code%

命令endlocal不修改errorlevel

为了了解使用的命令及其工作原理,请打开 command prompt window,在其中执行以下命令,并仔细阅读为每个命令显示的所有帮助页面。

  • call /?
  • cls /?
  • echo /?
  • endlocal /?
  • for /?
  • if /?
  • popd /?
  • pushd /?
  • set /?
  • setlocal /?