在回答一个较旧的问题时,让我感到震惊的是find
,在以下示例中,它可能会多次处理文件:
find dir -type f -name '*.txt' \
-exec sh -c 'mv "$1" "${1%.txt}_hello.txt"' sh {} ';'
Run Code Online (Sandbox Code Playgroud)
或者更有效率
find dir -type f -name '*.txt' \
-exec sh -c 'for n; do mv "$n" "${n%.txt}_hello.txt"; done' sh {} +
Run Code Online (Sandbox Code Playgroud)
该命令可以查找.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 {} ';'
Run Code Online (Sandbox Code Playgroud)
在这里,我们不查找文件而是查找目录,并且for
内部sh
脚本的循环在第一次迭代之前评估其范围一次,因此我们没有相同的潜在问题。
GNUfind
手册没有明确说明这一点,OpenBSDfind
手册也没有明确说明。
可以find
找到在目录中运行时创建的文件吗?
简而言之:是的,但这取决于实现。最好编写条件以便忽略已处理的文件。
如前所述,POSIX 不做任何保证,就像它也不保证底层readdir()
系统调用一样:
如果在最近一次调用
opendir()
或之后从目录中删除或添加文件,则未指定rewinddir()
后续调用是否readdir()
返回该文件的条目。
我find
在我的 Debian(GNU find, Debian package version 4.6.0+git+20161106-2
)上测试了它。strace
表明它在做任何事情之前读取了完整的目录。
多浏览一下源代码会使 GNU find 似乎使用 gnulib 的一部分来读取目录,并且在gnulib/lib/fts.c 中有这个(gl/lib/fts.c
在find
tarball 中):
/* 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
Run Code Online (Sandbox Code Playgroud)
我将该限制更改为 100,然后
mkdir test; cd test; touch {0000..2999}.foo
find . -type f -exec sh -c 'mv "$1" "${1%.foo}.barbarbarbarbarbarbarbar"' sh {} \; -print
Run Code Online (Sandbox Code Playgroud)
导致像这个文件这样热闹的结果,它被重命名了五次:
1046.barbarbarbarbarbarbarbar.barbarbarbarbarbarbarbar.barbarbarbarbarbarbarbar.barbarbarbarbarbarbarbar.barbarbarbarbarbarbarbar
显然,需要一个非常大的目录(超过 100 000 个条目)来触发对 GNU find 的默认构建的影响,但是没有缓存的简单的 readdir+process 循环会更容易受到攻击。
理论上,如果操作系统总是按照readdir()
返回文件的顺序最后添加重命名的文件,那么像这样的简单实现甚至可能陷入无限循环。
在 Linux 上,readdir()
在 C 库中是通过getdents()
系统调用实现的,它已经一次返回多个目录条目。这意味着以后对 的调用readdir()
可能会返回已删除的文件,但对于非常小的目录,您可以有效地获得起始状态的快照。其他系统我不知道。
在上面的测试中,我故意将文件名重命名为更长的文件名:以防止文件名就地被覆盖。无论如何,对相同长度重命名的相同测试也进行了两次和三次重命名。这是否以及如何重要当然取决于文件系统内部。
考虑到所有这些,通过使find
表达式与已经处理的文件不匹配来避免整个问题可能是谨慎的。也就是说,添加-name "*.foo"
到我的示例或问题! -name "*_hello.txt"
中的命令中。
归档时间: |
|
查看次数: |
163 次 |
最近记录: |