18

我正在编写一个简单的 .bat 文件,但遇到了一些奇怪的行为。有几个地方我必须做一个简单的 if/else,但块内的代码似乎不能正常工作。

这是一个演示错误的简单案例:

@echo off

set MODE=FOOBAR

if "%~1"=="" (
  set MODE=all
  echo mode: %MODE%
) else (
  set MODE=%~1
  echo mode: %MODE%
)
echo mode: %MODE%

我得到的输出是:

C:\>test.bat test
mode: FOOBAR
mode: test

为什么代码块内的回显没有得到变量的新值?在我正在编写的实际代码中,我需要构建一些变量并在 if/else 的范围内引用它们。我可以将其切换为使用标签和 goto 而不是 if/else,但这似乎并不那么干净。

是什么导致了这种行为?代码块中的变量是否有某种限制?

4

4 回答 4

26

您遇到了 cmd 的静态变量扩展问题。MODE 变量只计算一次。如果您省略 @echo 离线,您可以看到这一点。

从集/?文档:

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

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

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

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

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

因为它不会在当前目录中建立文件列表,而是将 LIST 变量设置为找到的最后一个文件。同样,这是因为在读取 FOR 语句时 %LIST% 只扩展了一次,而此时 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%
于 2008-11-20T15:26:58.077 回答
2

setlocal EnableDelayedExpansion

将启用 /v 标志

于 2009-09-10T19:01:55.217 回答
0

另外这里已经回答了。

长篇回答文章:

https://rsdn.org/article/winshell/NTCommandProcessor.xml

谷歌翻译的变体很糟糕,但你至少可以阅读原始文本:

https://translate.google.com/?sl=ru&tl=en&text=https%3A%2F%2Frsdn.org%2F%3Farticle%2Fwinshell%2FNTCommandProcessor.xml&op=translate

Conditional block从章节开始阅读。

简短的回答:

块运算符(...)阻止 %-variable 扩展直到最上面的范围。您必须退出范围才能按原样使用 %-variable:

@echo off

set "MODE="
(
  (
    set MODE=all
    echo MODE=%MODE%
  )
  echo MODE=%MODE%
)
echo MODE=%MODE%

或者使用call前缀重新评估它:

@echo off

set "MODE="
(
  (
    set MODE=all
    call echo MODE=%%MODE%%
  )
)
于 2021-12-05T14:11:40.170 回答
-2

看起来读写使用不同的范围规则。

如果你消除这条线

set MODE=FOOBAR

它将按预期工作。因此,如果 if/elses 可以根据需要填充变量,您可能需要有一个复杂的系列。

于 2008-11-20T15:30:09.787 回答