17

I need to exclude .git from my find search. To achive that, I am using the -path ./.git -prune switch:

$ find . -path ./.git -prune -o \( -type f -o -type l -o -type d \) | grep '.git'
./.git

However, even though this skips the contents of the .git directory, it lists the directory itself. It works when I add -path ./.git -prune -o -print -a

find . -path ./.git -prune -o -print -a \( -type f -o -type l -o -type d \) | grep '.git'

Why is this necessary. I thought that both commands should have same output. The second syntax is quite ugly.

3

6 Answers 6

31

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.

1
  • My man page (Arch Linux, find-4.6.0) says, "If the whole expression contains no actions other than -prune or -print, -print is performed on all files for which the whole expression is true." But works as you describe.
    – x-yuri
    Commented Jun 17, 2017 at 22:56
5

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.

1

from man page,

To ignore a whole directory tree, use -prune rather than checking every file in the tree. For example, to skip the directory `src/emacs' and all files and directories under it, and print the names of the other files found, do something like this:

find . -path ./src/emacs -prune -o -print

So, may be you can use:

find . -path ./.git -prune -o -print

This will not lists the .git directory.

to specific a file name, you can do like this:

find . -path ./.git -prune , -name filename

Please notice the , comma operator.

4
  • can you please elaborate a little bit more on the comma (,) because i never saw it used for find and it is not even mentioned at the man page. Commented Mar 9, 2021 at 8:31
  • @DJCrashdummy, please search OPERATORS section in the man page. refer this page: man7.org/linux/man-pages/man1/find.1.html
    – jianyongli
    Commented Mar 9, 2021 at 9:11
  • if i understood your answer correct, the result of find "$HOME/" -path "$HOME/.*" -prune -o \( -empty -print \) and find "$HOME/" -path "$HOME/.*" -prune , -empty should be the same... but i do get with the later still empty dot files & dot folders in the output!?! Commented Mar 10, 2021 at 15:54
  • what's different?
    – jianyongli
    Commented Mar 11, 2021 at 12:25
0

To list all files except those in .git I use this command

find -type f -not -path './.git/*'
0

The best way to ignore specific paths in find command is to use find with exclude option of ls command as mentioned below. i have folders mumbai, dubai,tokyo, delhi, london, rome in /var/www/html. will exclude here london and tokyo folder to list all files from rest of folders.

ls /var/www/html |grep  -v 'london\|tokyo'  #ls exclude  example is mentioned here.

find exclude tip is shown below:-

find $(ls /var/www/html |grep  -v 'london\|tokyo' ) -type f #run above exclude option with find command as mentioned here. 
-1

Here's a quick and dirty alternative:

find | fgrep -v /.git

I simply can't remember find's complex syntax...

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .