2009-12-03 20:51:48 +0000 2009-12-03 20:51:48 +0000
72
72

批量文件中的变量在IF里面时无法设置?

我有两个非常简单的批处理文件的例子。

给一个变量赋值:

@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命令的文本,正如我所期望的那样:

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

这是为什么呢?

什么情况?


跟进问题。有什么办法可以在不使用setlocal的情况下启用延迟扩展?

如果我从另一个简单的批处理文件里面调用这个简单的例子,这个例子设置了FOO,但只是局部的。

例如:

testcaller.bat

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

test.bat

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

这样就会显示。

0x1&

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

答案 (6)

86
86
86
2009-12-03 21:35:26 +0000

批处理文件中的环境变量在解析一行时就会展开。如果是以括号分隔的块(如您的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
)

永远不会显示消息,因为当读取第一条IF语句时,_两条%VAR%语句中的IF被替换,因为它在逻辑上包括了IF的主体,这是一条复合语句。所以复合语句里面的IF其实是在比较 "之前 "和 "之后",两者永远不会相等。同样,下面的例子也不会像预期的那样工作:

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%
```。
4
4
4
2011-03-26 16:26:14 +0000

同样的行为也发生在单行命令上(&是命令分隔符):

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

Joey的解释是我最喜欢的。但是请注意,enabledelayedexpansion在Windows NT 4.0上不适用(我不确定Windows 2000是否适用)。

关于你的后续问题,不,没有EnableDelayedExpansion是不行的。然而,最初的行为与你的行为相反,可以用来解决第二个问题:诀窍是在同一行中setlocal再次设置你需要的变量值。

这是你修改后的endlocal

@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

0x1&

3
3
3
2009-12-03 21:02:01 +0000

如果不是这样,你很可能开启了环境变量的延迟扩展。你可以用cmd /V:OFF关闭它,或者在if中使用感叹号。

@echo off
IF NOT DEFINED BAR (
    set FOO=1
    echo FOO: !FOO!
)
pause
echo on
1
1
1
2013-11-27 11:52:14 +0000

出现这种情况是因为你的带有FOR命令的行只评估一次。你需要一些方法来重新评估它。你可以用CALL命令模拟一个延迟扩展。

for /l %%I in (0,1,5) do call echo %%RANDOM%%
0
0
0
2016-12-28 21:46:34 +0000

有时候,就像我的情况一样,当你需要兼容旧系统,比如旧的NT4服务器,延迟扩展不能工作,或者因为某些原因不能工作,你可能需要替代的解决方案。

如果你将使用Delayd扩展,也建议至少应该包含批量检查。

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
0
0
0
2012-06-26 15:36:32 +0000

值得注意的是,如果是同一行的单条命令,它确实有效。

例如

if not defined BAR set FOO=1

实际上会将 FOO 设置为 1。

if not defined BAR echo FOO: %FOO%

嘿,我从来没有说过这很好看。但如果你不想搞setlocal或延迟扩展业务,这是一个快速的变通方法。