3

该命令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在这种情况下表现如何?假设有几个子目录sub1, sub2, sub3, 每个都包含上面的文件列表;forfiles /S当前正在迭代sub2sub1已经被处理,但sub3还没有;sub1和的内容sub3在那个时候发生了变化(当当前sub2如上所述通过时);那会列举什么?我想,内容的变化sub1不会被识别,但是呢sub3

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

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

4

2 回答 2

2

forfilesERROR: The system cannot find the file specified.一旦您尝试使用解析为不存在文件的@-变量,将无法继续枚举重命名的文件夹。删除的文件不会有错误,如果它的名称遵循当前使用的枚举顺序当前处理的文件,它将看到一个新添加的文件(我已经用默认的字母升序对其进行了测试)。因此,显然它不会在执行命令之前构建整个文件列表,而是在自定义命令完成后一一枚举它们。

根据您需要对forfiles可靠解决方案执行的操作,以仅列表模式解析dir /s /bor的输出。robocopy因此,您可以确保在任何更改之前生成列表。

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

  • for /f "tokens=*" %%a in ('robocopy /L /njh /njs /ndl ........') do ...
    适用于更复杂的场景,如限制日期跨度,可能需要使用额外的解析和/v非直截了当的情况。

于 2015-08-18T20:58:51.383 回答
2

我做了一些测试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被迭代,然后完成以下任务:

  • in sub3,目前正在迭代,
    • 删除file1.txt(已经迭代);
    • 删除file3.txt(尚未迭代);
    • 创建file4.txt(新项目,因此尚未迭代);
  • 删除容器sub1(已经迭代);
  • 删除容器sub4(尚未迭代);
  • 通过重命名来更改sub2(已经迭代)的内容;file2.txtfile4.txt
  • 通过重命名来更改sub5(尚未迭代)的内容;file2.txtfile4.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达到,就会返回一个错误,因为在迭代期间sub3sub4已被删除。sub5检测到内容的修改。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.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

在这个实验中,我们重命名file99.txtfile999.txtas soon as file1.txtis iterated。

要执行的命令行是:

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 个文件。

在这个实验中,我们重命名file99.txtfile100.txtas soon as file1.txtis iterated。

要执行的命令行是:

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.txtfile999.txtas soon as file1.txtis iterated。

要执行的命令行是:

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。 似乎有一种缓冲区,其中枚举了一些项目,一旦迭代需要更多数据,枚举就会继续下一部分,依此类推,直到到达终点。

于 2015-08-18T21:49:19.437 回答