回答的同时一个较旧的问题让我惊讶的是find
,在下面的示例中,似乎可能会多次处理文件:
find dir -type f -name '*.txt' \
-exec sh -c 'mv "$1" "${1%.txt}_hello.txt"' sh {} ';'
或者更有效率的
find dir -type f -name '*.txt' \
-exec sh -c 'for n; do mv "$n" "${n%.txt}_hello.txt"; done' sh {} +
该命令查找.txt
文件并将其文件名后缀从 更改.txt
为_hello.txt
.
这样做时,目录将开始积累名称与*.txt
模式匹配的新文件,即这些_hello.txt
文件。
问题:为什么它们实际上没有被 处理find
?因为根据我的经验,它们不是,我们也不希望它们是,因为这会引入一种无限循环。顺便说一句,mv
替换为也是这种情况。cp
这POSIX 标准说(我的重点)
如果从正在搜索的目录层次结构中删除或添加文件未指定是否
find
在搜索中包含该文件。
由于未指定是否会包含新文件,也许更安全的方法是
find dir -type d -exec sh -c '
for n in "$1"/*.txt; do
test -f "$n" && mv "$n" "${n%.txt}_hello.txt"
done' sh {} ';'
在这里,我们不查找文件而是查找目录,并且for
内部sh
脚本的循环在第一次迭代之前评估其范围一次,因此我们不会遇到相同的潜在问题。
GNUfind
手册没有明确说明这一点,OpenBSDfind
手册也没有明确说明。
答案1
可以find
找到在遍历目录时创建的文件吗?
简而言之:是的,但这取决于实施。最好编写条件,以便忽略已处理的文件。
如前所述,POSIX 不保证任何一种方式,就像它也保证不保证底层readdir()
系统调用:
如果在最近一次调用
opendir()
或后从目录中删除或添加文件rewinddir()
,则后续调用是否readdir()
返回该文件的条目是未指定的。
我find
在我的 Debian(GNU find,Debian 软件包版本4.6.0+git+20161106-2
)上进行了测试。strace
显示它在执行任何操作之前读取了完整目录。
多浏览一下源代码,看起来 GNU find 使用 gnulib 的一部分来读取目录,这在gnulib/lib/fts.c(gl/lib/fts.c
在find
压缩包中):
/* If possible (see max_entries, below), read no more than this many directory
entries at a time. Without this limit (i.e., when using non-NULL
fts_compar), processing a directory with 4,000,000 entries requires ~1GiB
of memory, and handling 64M entries would require 16GiB of memory. */
#ifndef FTS_MAX_READDIR_ENTRIES
# define FTS_MAX_READDIR_ENTRIES 100000
#endif
我将限制更改为 100,然后执行了
mkdir test; cd test; touch {0000..2999}.foo
find . -type f -exec sh -c 'mv "$1" "${1%.foo}.barbarbarbarbarbarbarbar"' sh {} \; -print
导致了像这个文件这样搞笑的结果,它被重命名了五次:
1046. 巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴巴
显然,需要一个非常大的目录(超过 100 000 个条目)才能在 GNU find 的默认构建上触发该效果,但没有缓存的简单 readdir+process 循环将更容易受到攻击。
理论上,如果操作系统总是按照返回文件的顺序最后添加重命名的文件readdir()
,那么像这样的简单实现甚至可能会陷入无限循环。
在Linux上,readdir()
C库中是通过getdents()
系统调用实现的,它已经一次性返回多个目录项。这意味着稍后的调用readdir()
可能会返回已删除的文件,但对于非常小的目录,您将有效地获得起始状态的快照。其他系统我不知道。
在上面的测试中,我故意重命名为更长的文件名:以防止文件名被就地覆盖。无论如何,对相同长度重命名的相同测试也进行了两次和三次重命名。当然,这是否重要以及如何重要取决于文件系统的内部结构。
find
考虑到所有这些,通过表达来避免整个问题可能是明智的不是匹配已处理的文件。也就是说,添加-name "*.foo"
到我的示例或! -name "*_hello.txt"
问题中的命令中。