在 IF 内部时未设置批处理文件中的变量?

Bro*_*own 82 windows batch command-line

我有两个非常简单的批处理文件示例:

给变量赋值:

@echo off
set FOO=1
echo FOO: %FOO%
pause
echo on
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,结果是:

FOO: 1 
Press any key to continue . . .
Run Code Online (Sandbox Code Playgroud)

但是,如果我将相同的两行放在 IF NOT DEFINED 块中:

@echo off
IF NOT DEFINED BAR (
    set FOO=1
    echo FOO: %FOO%
)
pause
echo on
Run Code Online (Sandbox Code Playgroud)

出乎意料地导致:

FOO: 
Press any key to continue . . .
Run Code Online (Sandbox Code Playgroud)

应该与 IF没有任何关系,显然该块正在被执行。如果我在 if 上方定义 BAR,则只显示 PAUSE 命令中的文本,正如预期的那样。

是什么赋予了?


跟进问题:有没有办法在没有 setlocal 的情况下启用延迟扩展?

如果我从另一个内部调用这个简单的示例批处理文件,则该示例设置 FOO,但仅限于 LOCALLY。

例如:

测试调用者.bat

@call test.bat 
@echo FOO: %FOO% 
@pause 
Run Code Online (Sandbox Code Playgroud)

测试.bat

@setlocal EnableDelayedExpansion 
@IF NOT DEFINED BAR ( 
    @set FOO=1 
    @echo FOO: !FOO! 
) 
Run Code Online (Sandbox Code Playgroud)

这显示:

FOO: 1 
FOO: 
Press any key to continue . . . 
Run Code Online (Sandbox Code Playgroud)

在这种情况下,看来我必须在 CALLER 中启用延迟扩展,这可能很麻烦。

Joe*_*oey 99

解析一行时,批处理文件中的环境变量会被扩展。对于由括号分隔的块(作为您的if defined),整个块算作“行”或命令。

这意味着在块运行之前,所有出现的 %FOO% 都被它们的值替换。在您什么都没有的情况下,因为变量还没有值。

要解决这个问题,您可以启用延迟扩展

setlocal enabledelayedexpansion
Run Code Online (Sandbox Code Playgroud)

延迟扩展会导致由感叹号 ( !)分隔的变量在执行时被评估而不是解析,这将确保您的情况下的正确行为:

if not defined BAR (
    set FOO=1
    echo Foo: !FOO!
)
Run Code Online (Sandbox Code Playgroud)

help set 也详细说明了这一点:

最后,增加了对延迟环境变量扩展的支持。默认情况下始终禁用此支持,但可以通过/V命令行开关启用/禁用CMD.EXE. 看CMD /?

延迟环境变量扩展对于绕过当前扩展的限制非常有用,该限制发生在读取一行文本时,而不是执行时。以下示例演示了立即变量扩展的问题:

set VAR=before
if "%VAR%" == "before" (
    set VAR=after
    if "%VAR%" == "after" @echo If you see this, it worked
)
Run Code Online (Sandbox Code Playgroud)

永远不会显示消息,因为%VAR%在读取第一个 IF 语句时,两个 IF语句中的the都会被替换,因为它在逻辑上包含了 的主体IF,这是一个复合语句。所以复合语句中的 IF 实际上是在比较“before”和“after”,后者永远不会相等。同样,以下示例也不会按预期工作:

set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%
Run Code Online (Sandbox Code Playgroud)

因为它不会在当前目录中建立文件列表,而只会将LIST 变量设置为找到的最后一个文件。同样,这是因为在 读取语句%LIST%时只扩展了一次FOR,并且此时LIST变量为空。所以我们正在执行的实际 FOR 循环是:

for %i in (*) do set LIST= %i
Run Code Online (Sandbox Code Playgroud)

它只是保持设置LIST为找到的最后一个文件。

延迟环境变量扩展允许您在执行时使用不同的字符(感叹号)来扩展环境变量。如果启用了延迟变量扩展,则上述示例可以编写如下以按预期工作:

set VAR=before
if "%VAR%" == "before" (
    set VAR=after
    if "!VAR!" == "after" @echo If you see this, it worked
)

set LIST=
for %i in (*) do set LIST=!LIST! %i
echo %LIST%
Run Code Online (Sandbox Code Playgroud)

  • 如果您需要回显 `!`,请使用 `^^^!`(转义两次)。否则“延迟扩展”功能会吃掉它。 (18认同)

dol*_*men 5

当命令位于单行时(&是命令分隔符)也会发生相同的行为:

if not defined BAR set FOO=1& echo FOO: %FOO%
Run Code Online (Sandbox Code Playgroud)

乔伊的解释是我最喜欢的。但请注意,这enabledelayedexpansion在 Windows NT 4.0 上不起作用(我不确定 Windows 2000 是否也如此)。

关于您的后续问题,不,没有 是不可能EnableDelayedExpansionsetlocal。然而,不利于您的原始行为可以用来解决第二个问题:技巧是endlocal在同一行中再次设置所需变量的值。

这是您test.bat修改后的:

@echo off
setlocal EnableDelayedExpansion 
IF NOT DEFINED BAR ( 
    set FOO=1 
    echo FOO: !FOO! 
)
endlocal & set FOO=%FOO%
Run Code Online (Sandbox Code Playgroud)

但这里有另一个解决该问题的方法:使用同一文件中的过程而不是内联块或外部文件。

@echo off
if not defined BAR call :NotDefined
pause
goto :EOF

:NotDefined
set FOO=1
echo FOO: %FOO%
goto :EOF
Run Code Online (Sandbox Code Playgroud)