“forfiles”在哪一点枚举目录(树)?

时间:2015-08-18 18:26:02

标签: batch-file for-loop cmd forfiles

命令forfiles旨在枚举目录并对每个项目应用(a)某些命令。使用/S可以完成相同的完整目录树。

forfiles命令正文中的命令更改枚举目录(树)的内容时会发生什么?

假设我们的目录D:\data包含以下内容:

file1.txt
file2.txt
file3.txt

forfiles /P "D:\data" /M "*.txt" /C "cmd /C echo @file"在所述目录中执行时的输出显然会反映上面的列表。

但是,当正文中的命令修改目录内容时,forfiles的输出是多少?例如,在实际迭代之前,列表中的一个文件被删除,比方说file3.txt?或者,如果在完成循环之前创建了一个新文件,如file4.txt

forfiles /S在这种情况下的行为如何?假设有几个子目录sub1sub2sub3,每个子目录包含上面的文件列表; forfiles /S目前正在迭代sub2sub1已经处理过,但sub3尚未处理; sub1sub3的内容会在此时更改(当前正在通过sub2进行处理);那将会列举什么?我想,sub1内容的更改将无法识别,但sub3会怎样?

我主要感兴趣的是自Windows Vista以来forfiles的行为。

注意:
我已经发布了关于for命令的a very similar question。但是,由于forfiles不是内置命令并且语法完全不同,所以我决定发布一个单独的问题而不是扩展另一个问题的范围。

2 个答案:

答案 0 :(得分:1)

一旦您尝试使用解析为不存在的文件的@ -variable,

forfiles将无法继续枚举带有ERROR: The system cannot find the file specified.的重命名文件夹。删除文件没有错误,如果其名称遵循当前使用的枚举顺序中当前处理的文件,它将看到新添加的文件(我已按默认字母排序按升序测试)。显然,它不会在执行命令之前构建整个文件列表,而是在自定义命令完成后逐个枚举它们。

根据您对forfiles的具体要求,可靠的解决方案是在仅列表模式下解析dir /s /brobocopy的输出。因此,您可以确保在任何更改之前生成列表。

  • for /f "delims=" %%a in ('dir "d:\data\*.txt" /s /b') do .......
    适用于简单的枚举

  • for /f "tokens=*" %%a in ('robocopy /L /njh /njs /ndl ........') do ...
    适用于限制日期范围等更复杂的情况,可能需要在非直接情况下使用额外的解析和/v

答案 1 :(得分:1)

我用forfiles做了一些测试 - 这是结果......

目的&范围

此处的测试用例旨在证明forfiles在迭代所有(子)项之前是否完成给定目录(树)的枚举。

下面的列表显示了此处测试涵盖的模式:

  • 文件模式(/M)始终为*.txt;
  • 文件模式(/M)仅匹配文件,但不匹配目录;
  • 总是有一个根搜索路径(/P);
  • 正文中的
  • /C)仅使用内部cmd.exe命令(前缀为cmd /C);
  • 非递归操作迭代几个文件和一百个文件;
  • 递归操作(/S)只迭代几个目录;
  • 递归操作(/S)迭代目录层次结构,深度为一级;
  • 文件年龄过滤器(/D)根本没有使用;
  • 目录(树)内容仅在特定迭代期间修改一次;
  • 文件(内容)未被修改,因此未检测到大小和日期/时间更改;

测试设置

所有测试都在NTFS格式磁盘上执行。 (这可能是forfiles按字母顺序枚举所有文件的原因。)
操作系统是Microsoft Windows 7 64位(版本6.1.7601)。

预要件

在执行相应命令行之前,需要提前促进各个测试步骤中描述的所需目录树。
使用的根目录D:\Data中不得出现任何其他文件或目录。

forfiles /S,递归

对于此处的测试用例,必须建立以下目录树:

D:\Data\
+---sub1\
|       file1.txt
|       file2.txt
|       file3.txt
+---sub2\
|       file1.txt
|       file2.txt
|       file3.txt
+---sub3\
|       file1.txt
|       file2.txt
|       file3.txt
+---sub4\
|       file1.txt
|       file2.txt
|       file3.txt
+---sub5\
        file1.txt
        file2.txt
        file3.txt

我使用以下代码行进行设置:

@(pushd D:\Data
md sub1 & pushd sub1 & rem.> file1.txt & rem.> file2.txt & rem.> file3.txt & del file4.txt & popd
md sub2 & pushd sub2 & rem.> file1.txt & rem.> file2.txt & rem.> file3.txt & del file4.txt & popd
md sub3 & pushd sub3 & rem.> file1.txt & rem.> file2.txt & rem.> file3.txt & del file4.txt & popd
md sub4 & pushd sub4 & rem.> file1.txt & rem.> file2.txt & rem.> file3.txt & del file4.txt & popd
md sub5 & pushd sub5 & rem.> file1.txt & rem.> file2.txt & rem.> file3.txt & del file4.txt & popd
rd /S /Q sub6
popd) > nul 2>&1

我的目的是等到文件夹file2.txt中的sub3项被迭代,然后完成以下任务:

    目前正在迭代的sub3中的
    • 删除file1.txt(已经迭代);
    • 删除file3.txt(尚未迭代);
    • 创建file4.txt(新项目,因此尚未迭代);
  • 删除容器sub1(已经迭代);
  • 删除容器sub4(尚未迭代);
  • 通过将sub2重命名为file2.txt来更改file4.txt(已经迭代)的内容;
  • 通过将sub5重命名为file2.txt来更改file4.txt(尚未迭代)的内容;
  • 创建容器sub6(新项目,所以尚未迭代),在里面创建file4.txt;

对于所有迭代的项目,完整路径将回显到命令提示符。

如果在迭代所有项目之前完成枚举,则应该输出原始目录树,因此不应显示任何修改。

现在让我们看看会发生什么;这是要执行的命令行:

forfiles /S /P "D:\Data" /M "*.txt" /C "cmd /C (if @relpath==\".\sub3\file2.txt\" (del file1.txt & del file3.txt & rem.> file4.txt & rd /S /Q ..\sub1 & rd /S /Q ..\sub4 & ren ..\sub2\file2.txt file4.txt & ren ..\sub5\file2.txt file4.txt & md ..\sub6 & rem.> ..\sub6\file4.txt)) & echo @path"

输出如下:

"D:\Data\sub1\file1.txt"
"D:\Data\sub1\file2.txt"
"D:\Data\sub1\file3.txt"
"D:\Data\sub2\file1.txt"
"D:\Data\sub2\file2.txt"
"D:\Data\sub2\file3.txt"
"D:\Data\sub3\file1.txt"
"D:\Data\sub3\file2.txt"
"D:\Data\sub3\file3.txt"
ERROR: The system cannot find the file specified.
"D:\Data\sub5\file1.txt"
"D:\Data\sub5\file3.txt"
"D:\Data\sub5\file4.txt"

我们可以清楚地看到,这显然不是原始的目录树 看起来树中的目录在迭代之前被枚举,但是一旦迭代到达那里,就会枚举每个目录内容。 (至少对于手头的小树来说这是正确的;但是,在迭代之前,可能没有完全枚举具有高层次深度的巨大树的目录。)
删除sub1和修改sub2的内容不会被注意到。只要达到sub4,就会在迭代sub3期间返回错误,sub4已被删除。检测到sub5的内容的修改。 <{1}}是在迭代sub6期间创建的,根本无法识别。

sub3,非递归

对于没有forfiles选项的forfiles,使用平面目录树:

/S

这是使用以下代码段创建的:

D:\Data\
    file1.txt
    file2.txt
    file3.txt

对于测试,@(pushd D:\Data rem.> file1.txt & rem.> file2.txt & rem.> file3.txt & del file4.txt popd) > nul 2>&1 正文中的命令行检查当前文件是否为forfiles;如果是,则删除file2.txtfile1.txt,并创建新的file3.txt。当前文件将回显到命令提示符。

要执行的命令行是:

file4.txt

输出结果为:

forfiles /P "D:\Data" /M "*.txt" /C "cmd /C (if @fname==\"file2\" (del file1.txt & del file3.txt & rem.> file4.txt)) & echo @file"

这表示在迭代文件之前已经枚举了整个目录内容 但是,为了证明上述假设,让我们进行一些更密集的测试。

这一次,我们使用了一百个文件:

"file1.txt"
"file2.txt"
"file3.txt"

这些是使用以下代码创建的:

D:\Data\
    file0.txt
    file1.txt
    file2.txt
    ...
    file99.txt

在此实验中,只要迭代@(pushd D:\Data del file100.txt & del file999.txt for /L %%N in (0,1,99) do (echo.%%N> file%%N.txt) popd) > nul 2>&1 ,我们就会将file99.txt重命名为file999.txt

要执行的命令行是:

file1.txt

输出结果为:

forfiles /P "D:\Data" /M "*.txt" /C "cmd /C (if @fname==\"file1\" (ren file99.txt file999.txt)) & echo @file"

我们收到一份反映重命名的100个文件的列表,这意味着我们不会读取原始文件列表。因此,在迭代开始之前,枚举不会完成。

这里我们再次使用上述100个文件。

在此实验中,只要迭代"file0.txt" "file1.txt" "file10.txt" "file11.txt" ... "file98.txt" "file999.txt" ,我们就会将file99.txt重命名为file100.txt

要执行的命令行是:

file1.txt

输出结果为:

forfiles /P "D:\Data" /M "*.txt" /C "cmd /C (if @fname==\"file1\" (ren file99.txt file100.txt)) & echo @file"

现在我们只收到99个文件的列表,同时没有"file0.txt" "file1.txt" "file10.txt" "file11.txt" ... "file98.txt" file99.txt。似乎最后一个文件的枚举是在文件重命名后完成的,但是file100.txt没有显示,因为它会违反字母顺序(它应该出现在file100.txt之后,但是那个地方附近的文件似乎已被列举。)

我们再次使用上述100个文件。

在此实验中,只要迭代file10.txt,我们就会将file0.txt重命名为file999.txt

要执行的命令行是:

file1.txt

输出结果为:

forfiles /P "D:\Data" /M "*.txt" /C "cmd /C (if @fname==\"file1\" (ren file0.txt file999.txt)) & echo @file"

现在我们收到了包含"file0.txt" "file1.txt" "file10.txt" "file11.txt" ... "file98.txt" "file99.txt" "file999.txt" file0.txt的101个文件的列表。似乎file999.txt在重命名之前已经被枚举,但最后的文件还没有,所以file0.txt也出现在列表中。

结论

显然,在迭代所有(匹配)项目之前,file999.txt 枚举整个目录(树)。
似乎存在一种缓冲区,其中一些项被枚举,一旦迭代需要更多数据,枚举将继续下一部分,依此类推,直到达到结束。