在IF中时,是否未设置批处理文件中的变量?


69

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

给变量赋值:

@echo off
set FOO=1
echo FOO: %FOO%
pause
echo on

如预期的那样,将导致:

FOO: 1 
Press any key to continue . . .

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

@echo off
IF NOT DEFINED BAR (
    set FOO=1
    echo FOO: %FOO%
)
pause
echo on

出乎意料地导致:

FOO: 
Press any key to continue . . .

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

是什么赋予了?


后续问题:没有setlocal,有什么方法可以启用延迟扩展吗?

如果要从另一个内部调用此简单示例批处理文件,则该示例设置为FOO,但仅是LOCALLY。

例如:

测试调用程序

@call test.bat 
@echo FOO: %FOO% 
@pause 

测试棒

@setlocal EnableDelayedExpansion 
@IF NOT DEFINED BAR ( 
    @set FOO=1 
    @echo FOO: !FOO! 
) 

显示:

FOO: 1 
FOO: 
Press any key to continue . . . 

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

Answers:


84

解析行时,批处理文件中的环境变量将展开。对于用括号分隔的块(如您所指if defined),整个块都算作“行”或命令。

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

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

setlocal enabledelayedexpansion

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

if not defined BAR (
    set FOO=1
    echo Foo: !FOO!
)

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
)

永远不会显示该消息,因为%VAR%在读取第一个IF语句时,两个 IF语句中的in 都会被替换,因为它在逻辑上包含的主体IF,它是一个复合语句。因此,复合语句中的IF实际上是将“ before”与“ after”进行比较,这将永远是不相等的。同样,以下示例将无法正常工作:

set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%

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

for %i in (*) do set LIST= %i

只是将设置保留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%

17
而且,如果您需要回显!,请使用^^^!(将其转义两次)。否则,“延迟扩展”功能会吃掉它。
grawity

4

当命令在一行上时(&也就是命令分隔符),也会发生相同的行为:

if not defined BAR set FOO=1& echo FOO: %FOO%

乔伊的解释是我的最爱。但是请注意,enabledelayedexpansion这在Windows NT 4.0上不起作用(并且我不确定Windows 2000)。

关于你的后续问题,不,这是不可能EnableDelayedExpansion没有setlocal。但是,与您相反的原始行为可用于解决第二个问题:诀窍是endlocal在同一行上再次设置所需变量的值。

这是您的test.bat修改内容:

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

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

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

:NotDefined
set FOO=1
echo FOO: %FOO%
goto :EOF

“诀窍是在同一行上本地化”-谢谢!
dforce

3

如果这样无法正常工作,则可能是延迟了环境变量扩展。您可以使用以下命令将其关闭,也可以在其中cmd /V:OFF使用感叹号:

@echo off
IF NOT DEFINED BAR (
    set FOO=1
    echo FOO: !FOO!
)
pause
echo on

3
启用延迟扩展只会更改感叹号的语义。它对普通的变量扩展没有任何影响。此外,意外启用它的可能性很小。启用它的人通常是故意这样做的。
乔伊,

1

发生这种情况是因为带FOR命令的行仅计算一次。您需要一些重新评估的方法。您可以使用以下CALL命令模拟延迟扩展:

for /l %%I in (0,1,5) do call echo %%RANDOM%%

延迟的扩展不起作用,但是此/ do调用/在win7上起作用,对于我的3行cmd脚本可以找到真正的硬链接:@for / f“ delims =” %% a in('bash〜/ perl / cwd2 .sh%*')进行呼叫设置CWD_REAL = %% a echo cd / d%CWD_REAL%cd / d%CWD_REAL%
Mosh)

0

值得注意的是,如果它是同一行上的单个命令,它就可以工作。

例如

   if not defined BAR set FOO=1

将实际设置FOO为1。然后,您可以执行另一项检查,例如:

   if not defined BAR echo FOO: %FOO%

嘿,我从未说过这很漂亮。但是,如果您不想弄乱setlocal或延迟的扩展业务,这是一个快速的解决方法。


0

有时,就像我的情况一样,当您需要与旧系统(例如旧的NT4服务器)兼容时,由于延迟扩展无法正常工作或由于某些原因而无法正常工作,您可能需要替代解决方案。

如果您将使用“延迟扩展”,还建议至少应包括按批次签入:

IF NOT %Comspec%==!Comspec! ECHO Delayed Expansion is NOT enabled. 

如果您只想使用更具可读性和简单性的方法,请使用以下替代解决方案:

REM Option 2: use `call` inside block statement
IF NOT DEFINED BAR2 ( 
  set FOO2=2 
  call echo FOO2: %%FOO2%%
)
echo FOO2: %FOO2%

像这样工作:

>REM Option 2: use `call` inside block statement

>IF NOT DEFINED BAR2 (
 set FOO2=2
 call echo FOO2: %FOO2%
)
FOO2: 2

>echo FOO2: 2
FOO2: 2

以及另一种纯cmd解决方案:

REM Option 3: use `goto` logic blocks
IF DEFINED BAR3 goto endbar3
  set FOO3=3 
  echo FOO3: %FOO3%
:endbar3
echo FOO3: %FOO3%

它是这样的:

>REM Option 3: use `goto` logic blocks

>IF DEFINED BAR3 goto endbar3

>set FOO3=3

>echo FOO3: 3
FOO3: 3

>echo FOO3: 3
FOO3: 3

这是完整的IF THEN ELSE语法解决方案:

REM Full IF THEN ELSE solution
IF NOT DEFINED BAR4 goto elsebar4
REM this is TRUE condition, normal batch flow
  set FOO4=6
  echo FOO4: %FOO4%
  goto endbar4
:elsebar4
  set FOO4=4
  echo FOO4: %FOO4%
  set BAR4=4
:endbar4
echo FOO4: %FOO4%
echo BAR4: %BAR4%

它是这样的:

>REM Full IF THEN ELSE solution

>IF NOT DEFINED BAR4 goto elsebar4

>set FOO4=4

>echo FOO4: 4
FOO4: 4

>set BAR4=4

>echo FOO4: 4
FOO4: 4

>echo BAR4: 4
BAR4: 4
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.