-1

我试图嵌套两个forfiles循环,以便内循环的命令@从外循环和内循环迭代接收变量。对于后者,@需要为外部循环转义变量替换,以便内部forfiles命令接收变量名称。

我有一个枚举给定目录 ( C:\root) 的代码片段,如果迭代项本身就是一个目录,*.txt则列出所有包含的文本文件 ()。
但是,它没有按预期工作:我试图逃避@filewith的扩展\,但它扩展为外循环的值:

2> nul forfiles /P "C:\root" /M "*" /C "cmd /C if @isdir==TRUE forfiles /P @path /M *.txt /C \"cmd /C echo @relpath -- \@file\""

此外\@file, ^@file, ^^@file, ^^^@file, 0x40file0x40是 的十六进制字符代码表示@)扩展为外部forfiles变量的值。
\\@file^^^^@file扩展至外部值,分别为 with\^preceeded。
甚至@^file, @^^file(参见这篇文章)也不起作用(后者扩展为@file字面意思)。

那么:有没有办法从外部循环中逃避@变量的替换(例如@file,...),forfiles以便内部forfiles迭代接收文字变量名称并将其扩展为它的值?

我正在使用 Windows 7 64 位。


注意:
当当前处理的目录中没有文件与给定的掩码 ( )匹配时,2> nul重定向应该避免大量错误消息。ERROR: Files of type "*.txt" not found.*.txt

4

2 回答 2

1

这是一个解决方法 - forfiles 不是一个快速的野兽,所以任何额外的处理都不会很重要。

@echo off
for /f "delims=" %%a in ('2^>nul forfiles /P "C:\root" /M "*" /C "cmd /C if @isdir==TRUE echo @path"') do forfiles /P %%a /M *.txt /C "cmd /C echo @relpath -- @file"
pause
于 2015-09-10T03:03:50.633 回答
0

诀窍是使用forfilesformat 中的十六进制字符代码替换功能0xHH,可以自行嵌套。在这种情况下,00x7840使用 ,因此第一个(外部)forfiles循环将0x78部分替换为x,从而导致0x40,这又由第二个(内部)forfiles循环通过将其替换为 来解决@

一个简单的0x40不起作用,因为它在第一遍中forfiles替换十六进制代码,然后在第二遍中处理@变量,因此0x40file将由@filefirst 替换,然后由外forfiles循环扩展为当前迭代的项目。


以下命令行遍历给定的根目录并显示每个直接子目录的相对路径(由外forfiles循环迭代)和其中找到的所有文本文件(由内forfiles循环迭代):

2> nul forfiles /P "C:\root" /M "*" /C "cmd /C if @isdir==TRUE forfiles /P @path /M *.txt /C 0x22cmd /C echo @relpath -- 00x7840file0x22"

输出可能看起来像(左侧的相对子目录路径,右侧的文本文件):

.\data_dir -- "text_msg.txt"

.\logs_dir -- "log_book.txt"
.\logs_dir -- "log_file.txt"

代码说明:

  • 如上所述,该00x7840file部分对外部命令隐藏@file变量名forfiles并将其替换转移到内部forfiles命令;
  • 为避免引号"和的任何麻烦,通过说明它们的十六进制代码来避免外部切换cmd /C后字符串中的引号; (支持转义引号,但不关心,所以它检测到;没有特殊含义,所以它是安全的)/Cforfiles0x22
    forfiles\"cmd /C\"0x22cmd
  • if语句检查外forfiles循环枚举的项目是否为目录,如果不是,则跳过内forfiles循环;
  • 如果枚举的子目录不包含任何与给定模式匹配的项目,则forfiles返回一条错误消息ERROR: Files of type "*.txt" not found.,如 STDERR;为避免此类消息,2> nul已应用重定向;

逐步更换:

这是上面的命令行,但删除了重定向,仅用于演示:

forfiles /P "C:\root" /M "*" /C "cmd /C if @isdir==TRUE forfiles /P @path /M *.txt /C 0x22cmd /C echo @relpath -- 00x7840file0x22"

我们现在将提取将一个接一个执行的嵌套命令行。

取上述示例输出的第一行(.\data_dir -- "text_msg.txt")的项目,外部forfiles命令执行的命令行为:

cmd /C if TRUE==TRUE forfiles /P "C:\root" /M *.txt /C "cmd /C echo ".\data_dir" -- 0x40file"

所以内部forfiles命令行看起来像(cmd /C删除,并且if满足条件):

forfiles /P "C:\root" /M *.txt /C "cmd /C echo ".\data_dir" -- 0x40file"

现在内部forfiles命令执行的命令行是(注意周围删除的文字引号和变量的值.\data_dir的即时替换):0x40file@file

cmd /C echo .\data_dir -- "text_msg.txt"

像这样从最内层到最外层的命令行遍历这些步骤,您甚至可以嵌套两个以上的forfiles循环。


笔记:

所有与路径或文件名相关的@变量均由带引号的字符串替换;但是,上面显示的示例输出不包含目录路径的任何引号;这是因为在切换后从字符串forfiles中删除任何文字(非转义)引号;要将它们返回到此处的输出中,请在命令行中替换为; 也可以(但不推荐,但不要混淆)。"/C@relpath00x7822@relpath00x7822\\\"@relpath\\\"cmd


附录:

由于forfiles不是内部命令,因此应该可以在没有cmd /C前缀的情况下嵌套它,例如forfiles /C "forfiles /M *",例如(除非使用任何额外的内部或外部命令、命令连接、重定向或管道,其中cmd /C是强制性的)。
但是,由于/C切换后命令行参数的错误处理forfiles,实际上需要将其声明为forfiles /C "forfiles forfiles /M *",因此内部forfiles命令加倍。否则会抛出错误消息 ( ERROR: Invalid argument/option)。
在这篇文章中找到了这个最好的解决方法:forfiles without cmd /c(滚动到底部)。

于 2015-09-10T23:16:46.207 回答