在哪个时候 `forfiles` 枚举一个目录(树)?

3
命令forfiles旨在枚举目录并对每个项目应用(一个或多个)特定的命令。使用/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 还没有;在此时(当当前遍历到 sub2 时)更改了 sub1sub3 的内容;那么接下来会枚举什么?我猜,sub1 内容的更改不会被识别,但是 sub3 呢?

我主要关心的是自 Windows Vista 以来 forfiles 的行为。

注意:
我已经发布了一个关于for命令的非常相似的问题(链接)。然而,由于forfiles不是内置命令,并且具有完全不同的语法,因此我决定发布一个单独的问题,而不是扩展其他问题的范围。

2个回答

2

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


2

我进行了一些使用forfiles的测试--以下是结果...

目的与范围

这里的测试案例旨在证明forfiles是否在遍历所有(子)项目之前完成了对给定目录(树)的枚举。

下面的列表显示了这些测试所涵盖的模式:

  • 文件模式(/M)总是*.txt;
  • 文件模式(/M)仅匹配文件,而不是目录;
  • 始终有一个根搜索路径(/P);
  • 仅使用内部cmd.exe命令(以cmd / C为前缀)在主体(/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

我的意图是等待文件夹sub3中的项目file2.txt被迭代,然后执行以下任务:
  • 在当前迭代的sub3中,
    • 删除已经迭代的file1.txt
    • 删除尚未迭代的file3.txt
    • 创建尚未迭代的file4.txt
  • 删除已经迭代的容器sub1
  • 删除尚未迭代的容器sub4
  • 通过将file2.txt重命名为file4.txt来更改sub2(已经迭代)的内容;
  • 通过将file2.txt重命名为file4.txt来更改sub5(尚未迭代)的内容;
  • 创建尚未迭代的容器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内容的修改。在迭代sub3期间创建的sub6根本没有被识别。

forfiles,非递归

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

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.txt;如果是,则会删除 file1.txtfile3.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

在这个实验中,我们在迭代到file1.txt时,立即将file99.txt重命名为file999.txt
执行的命令行如下:
forfiles /P "D:\Data" /M "*.txt" /C "cmd /C (if @fname==\"file1\" (ren file99.txt file999.txt)) & echo @file"

输出结果为:
"file0.txt"
"file1.txt"
"file10.txt"
"file11.txt"
...
"file98.txt"
"file999.txt"

我们收到一个包含100个文件的列表,这些文件已经过重命名,意味着我们不会读取原始文件列表。因此,在迭代开始之前没有进行枚举。
在本实验中,我们再次使用上述100个文件。
在这个实验中,当迭代到file1.txt时,我们将file99.txt重命名为file100.txt
执行的命令行是:
forfiles /P "D:\Data" /M "*.txt" /C "cmd /C (if @fname==\"file1\" (ren file99.txt file100.txt)) & echo @file"

输出结果如下:
"file0.txt"
"file1.txt"
"file10.txt"
"file11.txt"
...
"file98.txt"

现在我们只收到了一个包含99个文件的列表,没有包括file99.txtfile100.txt。似乎最后几个文件的枚举是在文件重命名之后进行的,但是file100.txt并没有显示,因为这会违反字母顺序(它应该出现在file10.txt之后,但是附近的文件似乎已经被枚举过了)。
再次使用以上提到的100个文件。
在这个实验中,我们将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"

现在我们收到一个包含101个文件的列表,其中包括file0.txtfile999.txt。看起来file0.txt在重命名之前已经被枚举过了,但是最后的文件还没有被枚举,所以file999.txt也出现在列表中。


结论

显然,forfiles在迭代所有(匹配的)项之前没有枚举整个目录(树)。似乎有一种缓冲区,可以将某些项枚举出来,并且只要迭代需要更多数据,枚举就会继续下一个部分,依此类推,直到结束。


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