find: prune does not ignore specified path
I was confused as to why pruned directories were being printed by the find
command, too, and some other intricate details of how -prune
worked, but was able to figure it out with a few examples.
To run the examples below create the following directories and files.
mkdir aa
mkdir bb
touch file1
touch aa/file1
touch bb/file3
To create this structure:
$ tree
.
├── aa
│ └── file1
├── bb
│ └── file3
└── file1
Now use find to look for directories named aa
. No problem here.
$ find . -type d -name aa
./aa
Look for all directories other than aa and we get the current directory .
and ./bb
, which also makes sense.
$ find . -type d ! -name aa
.
./bb
So far so good, but when we use -prune
, find returns the directory we are pruning, which initially confused me because I was expecting it to return all the other directories and not the one being pruned.
$ find . -type d -name aa -prune
./aa
The reason why it returns the directory being pruned is explained, not in the -prune
section of the man pages as indicated in Timo's answer, but in the EXPRESSIONS
section:
If the expression contains no actions other than
-prune
,
which means that, since the expression matches the aa
directory name, then the expression will evaluate to true and it will be printed because find implicitly adds a -print
at the end of the entire command. It won't add a -print
, however, if you purposely add the action -o -print
to the end yourself:
find . -type d -name aa -prune -o -print
.
./file1
./bb
./bb/file3
Here the find command DOES NOT add an implicit -print
anymore, and so the directory we are pruning (aa
) won't get printed.
So finally, if we add a clause that searches for files with a filename pattern of file*
after the -o
, then you have to put a -print
at the end of that second clause like this:
find . \( -type d -name aa -prune \) -o \( -type f -name 'file*' -print \)
./file1
./bb/file3
The reason why this works is the same: If you don't put a -print
in the second clause, then since there is no action other than the -prune
action, find will add a -print
automatically at THE END of the command, causing the -prune
clause to print the pruned directory:
find . \( \( -type d -name aa -prune \) -o \( -type f -name 'file*' \) \) -print
./aa
./file1
./bb/file3
In general, you need to place the -print
command in the second clause. If you place it in the middle like the original poster did, it will not work correctly because the files being pruned will be printed immediately and second clause will not get a chance to pick the files it wants:
find . \( -type d -name aa -prune -o -print \) -o \( -type f -name 'file*' \)
.
./file1
./bb
./bb/file3
So unfortunately, the original poster got the command wrong above by placing the -print
in the wrong place. It might have worked for his specific case, but it doesn't work in the general case.
There are thousands of people who have difficulties understanding how -prune
works.
The find
man page should be updated in order to prevent the neverending worldwide confusion about this command.
The man
page for find
gives:
-prune True; if the file is a directory, do not descend into it. If
-depth is given, false; no effect. Because -delete implies
-depth, you cannot usefully use -prune and -delete together.
So in the first example it is not so that -path ./.git -prune
is untrue and therefore the default action (-print
) would not be called, hence the line is printed.