Find -exec mv stops after first exec

find

While researching for this question: Find and move directories based on file type and date, I stumbled upon a problem of my own.

First I created a bunch of directories with files in them:

seq 10 | while read dir; do
  mkdir dir$dir
  touch dir$dir/file.txt
  seq 5 | while read file; do
    touch dir$dir/file${dir}_$file
  done
done

Then I tried following find command:

$ find -type f -name "*.txt"
./dir6/file.txt
./dir8/file.txt
./dir10/file.txt
...

Works as expected.

Now with an -exec:

$ find -type f -name "*.txt" -exec sh -c 'echo mv -v "${0%/*}" ../bar' {} \;
mv -v ./dir6 ../bar
mv -v ./dir8 ../bar
mv -v ./dir10 ../bar
...

Works as expected.

Now the actual command:

$ find -type f -name "*.txt" -exec sh -c 'mv -v "${0%/*}" ../bar' {} \;
`./dir6' -> `../bar/dir6'

It stops after the first mv. No error message. Exit status is 0. The directory bar exists and dir6 is moved correctly.

When I execute again this happens:

$ find -type f -name "*.txt" -exec sh -c 'mv -v "${0%/*}" ../bar' {} \;
`./dir8' -> `../bar/dir8'

Again stop after the first mv. Exit status is 0. dir8 is moved correctly.

Why does find stop after the first mv? Because the directory is moved and find is confused? Why does it not print an error or return an error exit status?

When I create more directories and more files it has the same behaviour.

In the question linked above the problem is that find executes once without problem but print errors for all matches after that. Is this related?

Using find (GNU findutils) 4.4.2.

Best Answer

In:

find -type f -name "*.txt" -exec sh -c 'mv -v "${0%/*}" ../bar' {} \;

find opens the current directory (.), gets the content (list1). Then goes on to process that list. It processes dir6; as that file is a directory, it chdirs into it, opens it gets a second list of files (list2), processes file.txt and does:

mv -v ./dir6 ../bar

Now, find's current directory has not changed, but its path has changed: it is now ../bar/dir6. After finishing traversing the content of dir6, it goes back up (chdir("..")) and is now in ../bar. find detects that the directory is not the same as before and exits.

Related Question