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,  -print is performed on all files for which the expression is true.

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.

Tags:

Bash

Find