在 Linux 中,如果将目录中的 1000 个文件移动到另一个位置,同时将另外 300 个文件添加到源目录,会发生什么情况?

在 Linux 中,如果将目录中的 1000 个文件移动到另一个位置,同时将另外 300 个文件添加到源目录,会发生什么情况?

在 Linux 中,如果将目录中的 1000 个文件移动到另一个位置,并在移动原始 1000 个文件的同时将另外 300 个文件添加到源目录,会发生什么情况。目标最终会是 1300 个文件吗?还是源文件夹中会保留 300 个文件。

答案1

这取决于您使用的工具:让我们来看看几个案例:

如果您运行类似于 int a shell 的命令mv /path/to/source/* /path/to/dest/,则最终会移动原始的 1000 个文件,而新的 300 个文件保持不变。这是因为 shell 会在*开始移动操作之前扩展列表,因此在移动过程中,列表已经固定。

如果您使用 Nautilus(和其他 GUI 朋友),您最终会得到相同的结果:它将根据所选文件运行移动操作 - 当出现新文件时这不会改变。

如果你使用自己的程序,使用系统调用循环,glob并且只 mv直到glob保持为空,您最终会在新目录中找到所有 1300 个文件。这是因为每个新glob文件都会拾取在此期间出现的新文件。

答案2

当您告诉系统移动目录中的所有文件时,它会列出所有文件,然后开始移动它们。如果目录中出现新文件,则不会将它们添加到要移动的文件列表中,因此它们将保留在原始位置。

当然,您可以编写一种不同于mv定期检查源目录中新文件的移动文件的方法。

答案3

内核本身不能处于“移动 1000 个文件”操作的“中间”。您需要更具体地说明您提议的操作。

一个线程每次只能移动一个文件rename(*oldpath, const char *newpath)renameat系统调用(并且只能在同一个文件系统1内)。或者 Linuxrenameat2有类似标志,可以RENAME_EXCHANGE原子地交换两个路径名,或者RENAME_NOREPLACE不是如果目标存在则替换它。(例如,允许mv -i避免竞争条件的实现,stat然后rename,仍然会覆盖在之后创建的文件statlink+unlink也可以解决这个问题,因为link如果新名称存在则会失败。)

但每个系统调用只能重命名一个目录条目. 使用 POSIXrenameat和(用 开启)可让您继续循环遍历目录中的文件olddirfd,即使源目录或目标目录newdirfdopen(O_DIRECTORY)本身已被重命名。(使用相对路径也可以允许常规的rename()。)

无论如何,正如其他答案所说,大多数使用重命名系统调用的程序都会在执行第一个之前找出文件名列表rename。(通常使用readdir(3)POSIX 库函数作为 Linux 等平台特定系统调用的包装器getdents)。

但是如果您说的是find -exec ... {} \;每个文件运行一个命令,或者-exec {} +对于太多文件而无法在一条命令行上容纳的更高效命令,那么您当然可以在扫描的同时进行重命名。例如

find . -name '*.txt' -exec mv -t ../txtfiles {} \;   # Intentionally inefficient

如果你.txt在运行此程序时创建了一些新文件,那么可能中可以看到其中的一些../txtfiles。但内部find(1)将会使用open(O_DIRECTORY)和。getdents.

如果一个系统调用就足以返回全部目录条目.(find 将一次循环一个,仅在需要-type或递归时进行进一步的系统调用,或在匹配时进行 fork+exec),则列表是某一时间点的目录条目的快照。对目录的进一步更改不会影响所做的更改find,因为它已经有一个目录副本,列出了它将循环的内容。(可能它在内部使用readdir(3),它一次返回一个条目,但在 glibc 内部,我们从使用中知道strace find .它会getdents64使用条目的缓冲区大小进行系统调用count=32768。)

但是如果目录很大并且/或者内核没有填满find缓冲区,则在循环第一次获得的内容之后,它将不得不进行第二次 getdents 系统调用。因此,在进行一些重命名之后,它可能会查看新条目。

但请参阅其他答案下评论中的讨论:内核可能已经为我们快照了,因为(我认为)getdents不允许两次返回相同的文件名。不同的文件系统使用不同的排序/索引机制,使对大型目录中条目的访问比线性搜索更有效。因此,添加或删除目录可能会对其余条目的顺序产生其他影响。嗯,文件系统可能更有可能保持稳定的顺序,只更新实际索引(如EXT4dir_index功能),因此目录FD的位置可以只是一个要从中恢复的目录条目?我真的不知道库telldir(3)接口是如何映射到的lseek,或者这纯粹是用户空间的事情,用于循环遍历用户空间获得的缓冲区。但是getdents可能需要多个才能从一个巨大的目录中获取所有条目,所以即使不支持查找,内核也需要能够记录当前位置。


脚注1:

要在文件系统之间“移动”,则需要由用户空间来复制和取消链接。(例如使用openread+writemmap+writesendfile(2)或者copy_file_range(2),后两者完全避免了通过用户空间反弹文件数据。)

相关内容