嵌套批处理循环

29

以下的嵌套for循环让我抓狂(在Windows 7上):

@echo off
SetLocal EnableDelayedExpansion

set TESTDIRS=fast mid slow
set TD=src\test\resources\testsuite

for %%d in (%TESTDIRS%) do (
    set CTD=%TD%\%%d
    echo CTD: !CTD!
        REM Echos the expected path
    echo CTD: %CTD%
        REM Echos nothing -- understandable

    for /R !CTD! %%f in (*.fs) do (echo %%f)
        REM Echos nothing -- why?
    for /R src\test\resources\testsuite\fast %%f in (*.fs) do (echo %%f)
        REM Echos expected files
)

我尝试了各种禁用DelayedExpansion、调用语句等解决方案,但我从未让内部循环正常工作。我知道可以通过子程序调用来替换内部循环,但必须有一种方法可以使用嵌套循环使其正常工作。


我对批处理编程很新(虽然熟悉bash),而且我永远不会直觉地认为在for循环内设置的变量需要使用!!来表示它们的值... - RonJohn
6个回答

34

以下是一个可以工作的嵌套循环示例:

@echo off
SetLocal

set B=alpha beta gamma
set A=eins zwo

FOR %%b in (%B%) do (
  FOR %%a in (%A% %%b) DO (
    echo %%b -^> %%a
  )
)

输出结果(至少在Windows 7上)为

alpha -> eins
alpha -> zwo
alpha -> alpha
beta -> eins
beta -> zwo
beta -> beta
gamma -> eins
gamma -> zwo
gamma -> gamma

这支持jeb的观察,即在循环中,如果变量扩展发生在括号内(即使没有延迟扩展),也会起作用。


3
嗨,我使用了你的例子,但在最后一步中它会打印%%b而不是%%a,我通过在内部循环中只使用FOR %%a in (%A%)来解决了这个问题。 - Juan Antonio Orozco
@JuanAntonioOrozco 你可能已经注意到了预期的行为。我编辑了我的答案,以澄清循环应该打印什么。 - Malte Schwerhoff

16

因为没有人提到过它,所以这里介绍使用批处理子程序和 CALL 命令的解决方案。

@echo off

set TESTDIRS=fast mid slow
set TD=src\test\resources\testsuite

for %%d in (%TESTDIRS%) do call :process_testdir %%d
goto :eof

:process_testdir
set CTD=%TD%\%1
echo CTD: %CTD%
    REM Echos the expected path

for /R %CTD% %%f in (*.fs) do (echo %%f)
    REM Echos as expected

goto :eof

我知道GOTO并不是很受欢迎,但批处理文件最初就是为了使用标签来控制流程而设计的。括号控制结构语法是后来添加的,而这个问题就是它失效的一个例子。这个问题非常适合使用批处理子程序。


9
如果您使用pushd !CTD!popd,并让FOR /R默认使用当前目录,会怎样呢?

我会接受这个解决方案,因为它解决了我的问题,并且非常聪明 :-) - Malte Schwerhoff
5
这是一种以不同的方式解决特定问题的解决方法。它没有解释如何正确嵌套for循环(即没有回答问题)。我想用一个不同的用例来嵌套for循环,而这个解决方法无法解决我的问题,所以我通过谷歌搜索“批量嵌套for循环”来到这里。对我来说评分为-1。 - Algoman

6

这不是很明显!这是特殊解析FOR命令的结果!
FOR命令在转义/特殊字符阶段之后被直接解析(用于检测括号),因此您不能使用延迟或%%var扩展作为参数。

FOR %%a in (%%%%B) do (
  FOR %%a in (1) DO ( <<< this %%a will not replaced with %%B
      echo %%a - shows 1, because %%a is the name of the inner variable
      echo %%B - doesn't work
  )
)

而且这也无法起作用:
set chars=abc
FOR /F "delims=!chars!" %%N in (bla) DO ....  

不要将 a, bc 设置为定界符,而是使用 !, c, h, ar

编辑:在括号内,延迟扩展按预期工作:

set var=C:\temp
For %%a in (!var!) DO echo %%a

我认为你需要使用一个函数来解决你的问题。

1
很抱歉,我不明白你的回答如何帮助我解决我的问题,因为它只告诉我什么是不行的。此外,在我的问题中,我已经提到了用子程序替换内层循环的可能性,但我的问题是如何使嵌套循环正常工作。 - Malte Schwerhoff
2
抱歉,但我认为您只能使用Ben Voigt的PUSHD解决方案。由于路径仅在第一个括号的开头扩展一次,因此似乎无法进行扩展。 - jeb

1

引用Malte Schwerhoff的回答

如果您不想重复B,可以简单地添加“if”语句

@echo off
SetLocal

set B=alpha beta gamma
set A=eins zwo

FOR %%b in (%B%) do (
  FOR %%a in (%A% %%b) DO (
    IF %%b NEQ %%a (
        echo %%b -^> %%a
    )
  )
)

输出:

alpha -> eins
alpha -> zwo
beta -> eins
beta -> zwo
gamma -> eins
gamma -> zwo

0
根据FOR帮助文档,FOR /R“遍历以[驱动器:]路径为根的目录树,在树的每个目录中执行FOR语句。”
问题可能在于“遍历”这个词。我认为只有迈出第一步才算开始行走,如果没有迈出第一步,就不会有行走。由于此案例中的遍历从目录“fast”开始,且没有子目录,因此无法迈出第一步。
但是,如果将第二个FOR更改为:
for /R !CTD!\..\ %%f in (*.fs) do (echo %%f)

然后输出的结果是:

CTD: src\test\resources\testsuite\fast
CTD:
C:\FORTEST\src\test\resources\testsuite\fast\F1.fs
C:\FORTEST\src\test\resources\testsuite\mid\F2.fs
C:\FORTEST\src\test\resources\testsuite\slow\F3.fs
C:\FORTEST\src\test\resources\testsuite\fast\F1.fs
CTD: src\test\resources\testsuite\mid
CTD:
C:\FORTEST\src\test\resources\testsuite\fast\F1.fs
C:\FORTEST\src\test\resources\testsuite\mid\F2.fs
C:\FORTEST\src\test\resources\testsuite\slow\F3.fs
C:\FORTEST\src\test\resources\testsuite\fast\F1.fs
CTD: src\test\resources\testsuite\slow
CTD:
C:\FORTEST\src\test\resources\testsuite\fast\F1.fs
C:\FORTEST\src\test\resources\testsuite\mid\F2.fs
C:\FORTEST\src\test\resources\testsuite\slow\F3.fs
C:\FORTEST\src\test\resources\testsuite\fast\F1.fs

这是 OP 在寻找的内容吗?如果是,那是因为现在有一个可以递归的地方,即父目录。

(注意:在此测试中,fast、mid、slow 目录分别包含一个文件 F1.fs、F2.fs 和 F3.fs。)


这个答案将包括不在 %TESTDIRS% 中的目录。正如问题中所指出的那样,/R src\test\resources\testsuite\fast %%f 是有效的,这是 set CTD=%TD%\%%d 的预期结果,因此问题不太可能是对遍历树的理解误解。 - John Neuhaus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接