While answering an older question it struck me that it seems find
, in the following example, potentially would process files multiple times:
find dir -type f -name '*.txt' \
-exec sh -c 'mv "$1" "${1%.txt}_hello.txt"' sh {} ';'
or the more efficient
find dir -type f -name '*.txt' \
-exec sh -c 'for n; do mv "$n" "${n%.txt}_hello.txt"; done' sh {} +
The command finds .txt
files and changes their filename suffix from .txt
to _hello.txt
.
While doing so, the directories will start accumulating new files whose names matches the *.txt
pattern, namely these _hello.txt
files.
Question: Why are they not actually processed by find
? Because in my experience they aren't, and we don't want them to be either as it would introduce a sort of infinite loop. This is also the case with mv
replaced by cp
, by the way.
The POSIX standard says (my emphasis)
If a file is removed from or added to the directory hierarchy being searched it is unspecified whether or not
find
includes that file in its search.
Since it's unspecified whether new files will be included, maybe a safer approach would be
find dir -type d -exec sh -c '
for n in "$1"/*.txt; do
test -f "$n" && mv "$n" "${n%.txt}_hello.txt"
done' sh {} ';'
Here, we don't look for files but for directories, and the for
loop of the internal sh
script evaluates its range once before the first iteration, so we don't have the same potential issue.
The GNU find
manual does not explicitly say anything about this and neither does the OpenBSD find
manual.
find
utility not get concerned of such tricky behaviorreaddir
has essentially the same specification. I would expect it to be potentially filesystem-specific, even (loading one block of directory entries at a time is pretty reasonable).find
utility don't get concerned with the behavior. It means it's left to the authors of any given implementation of thefind
utility how to handle that case, rather than being specified. (If that seems unclear I recommend you fully clear up the words "specification" and "implementation" as they apply to software.)find
devs don't mention it). As described in ikkachu's answer, that's a filesystem level behavior, not even specified in thereaddir()
system call spec. There are going to be race conditions no matter what.