1

I have a folder full of source files for an application, the project uses git for version control and so there is a .git directory:

├── composer.json
├── .git
│   ├── branches
│   ├── config
│   ├── info
│   ├── logs
│   ├── objects
│   ├── packed-refs
│   └── refs
├── .gitignore
├── Gruntfile.js
├── less
│   ├── framework
│   ├── index.less
│   └── view_specific.less
├── package.json
[...]

I regularly need to do something with all the source files whilst ignoring the version control files, i.e. the .git directory and everything under it.
I normally use the -not -path option to find to exclude a directory, e.g.

find . -not -path "*git*" 

the problem with this is that it is too broad, e.g. it will exclude the .gitignore file, and any other files that have git in them, e.g. agitate, fugitive etc.
I discovered find with the -name and -prune switch does seem to target the .git directory and its contents:

% find . -name ".git" -prune -o -print | grep git
./.gitignore

but I don't understand how -name is matching the filenames.
From man find

   -name pattern
      Base  of  file  name (the path with the leading directories removed) matches shell pattern pattern.  

To me this implies, -name only matches when the pattern matches the last segment in a filename, e.g. -name foo would match /dir1/dir2/foo but not /dir1/dir2/foo/dir3/dir4/some_file to me the whole point of -path was for matching patterns like foo to files with pathnames like /dir1/dir2/foo/dir3/dir4/some_file.
It seems -name can match any segment in the filepath - not just the last segment - as long as its a complete match to the segment within the slashes?
Otherwise how does -name match file names?

2
  • The behavior you are observing is due to -prune, not -name (what do you think -prune is doing otherwise?). -name is behaving exactly as you expect it to. Also -path matches the whole path, not just any one directory in it.
    – phemmer
    Commented Jun 27, 2016 at 12:03
  • @Patrick to me it seems that name is responsible, because if I use find . -path ".git" -prune -o -print the .git directories and all the files underneath are not pruned, they are matched and printed, if I simply swap the -path to -name the .git VCS files do get pruned. Commented Jun 27, 2016 at 12:11

2 Answers 2

1

To ignore the .git directories and everything underneath them you need a construct like this

find . \( -name '.git' -prune \) -o \( -print {or whatever else you want to do} \)

This tells find that when it finds a file or directory called .git it's to prune its tree and not descend any further down that path. Everything else can be matched - and processed - within the other half of the or condition construct.

(The backslashes on the brackets stop them being processed by the shell, and for this simplistic example are technically un-necessary. I've left them in to make it explicitly obvious how the and and or constructs bind.)

Now, consider your concern with a folder called foo:

e.g. -name foo would match /dir1/dir2/foo but not /dir1/dir2/foo/dir3/dir4/some_file

You are correct, but I think you've missed the corollary. At some point find will reach /dir1/dir2/foo. The -name foo will match and so the -prune will be invoked. find cannot reach below foo without first seeing foo itself.

Consequently /dir1/dir2/foo/dir3/dir4/some_file can never be reached.

The initial result from inspection is that somehow -name foo matches /dir1/dir2/foo/dir3/dir4/some_file, but in reality it matched on /dir1/dir2/foo and abandoned any further inspection of that tree.

In contrast to -name foo, which matches foo only as the last component of a path, -path foo will match only if foo is a complete path. (find * -path foo might match a foo at the top level, but find . -path foo could never match ./foo.)

3
  • that explains perfectly, I didn't realise that -prune started at the parent directory, descended incrementally, then cut the entire subtree once it matched a filename, hence the subdirectories not even considered. really well written/explained answer, thanks! Commented Jun 27, 2016 at 12:58
  • @the_velour_fog it doesn't. -prune stops find descending a tree at the point that it's matched. The -name does the matching. Commented Jun 27, 2016 at 13:04
  • ok, my summary/paraphrasing of it probably didn't make sense, but yes, that's essentially how I understood your answer Commented Jun 27, 2016 at 13:09
1

Your understanding of -name is correct. It only matches the file name; the path leading to the file (the chain of containing directories) is irrelevant.

What you're missing is the effect of -prune. Contrast

find . -name ".git" -o -print 

which means “if it's called .git, then do nothing, else print the path and recurse into it if it's a directory” with

find . -name ".git" -prune -o -print 

which means “if it's called .git, then do not recurse into it, else print the path and recurse into it if it's a directory”.

3
  • thanks Gilles, so I am right to assume find will always perform the tests on the upper level, parent directories, before it descends into the folder to perform tests on child folders. In other words, you will never get a scenario where find checks a child folder first, prints, then later on, checks and prunes a parent/ancestor of the sub-directory. Commented Jun 28, 2016 at 1:12
  • @the_velour_fog Yes. Actually you can make find perform the tests and actions on a directory after traversing its children with the -depth global option. This option is incompatible with -prune. Commented Jun 28, 2016 at 1:22
  • ah! thats what -depth does, man find states the same thing, but I couldn't work it out from the man page. Commented Jun 28, 2016 at 1:24

You must log in to answer this question.

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