1

I want to list all the files except those inside .git/ directory with the find command only.

I tried this:

$ find . -path .git -prune -type f
$

it returned 0 files (instead of the 2 I have) so I tried this :

$  find . -not -path .git -type f | grep -c '\.git'
1770
$

I don't understand why none of these is working, can you help ?

EDIT0 : Thanks to @icarus, I see that the paths must be preceded by ./ and that I was missing the OR operator consequently, this command works :

$ find . -path ./.git -prune -o -type f | grep '\.git'
./.git
./.gitignore
$

EDIT1 : Why does that other command does not work ?

$ find . -not -path ./.git -a -type f | grep -c '\.git'
1770
$

EDIT2 : One last question : why does the -prune predicate need a -o -print not to print the ./.git and ./.gitignore results in the command below :

$ find . -name ".git*" -prune | grep git
./.git
./.gitignore
$ find . -name ".git*" -prune -o -print | grep -c git
0

EDIT3 : Thank you @rastafile :

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

It seems the GNU find -prune predicate calls -print implicitly when the previous expression is true :

If the expression contains no actions other than -prune, -print is performed on all files for which the expression is true.

In conclusion : The command I need is :

$ find . -name ".git*" -prune -o -type f -print

Thank you everyone.

2
  • 1
    short answer to EDIT2: one '-print' is added by find if there is none, even for '-prune'. So you have to steal the builtin '-print' it from '-prune'.
    – user373503
    Commented Feb 26, 2020 at 13:23
  • @rastafile Thanks a lot. Now I understand it better.
    – SebMa
    Commented Feb 26, 2020 at 13:46

2 Answers 2

2

There is a big difference between -path and -name -- they are not at all similar: "name" only looks at the last part, "path" at the whole chain of slashes. What a "filename" should mean, or a "pathname", is always a bit controversial. But here it is clear and important.

This works (you say):

find . -path ./.git -prune -o -type f 

You start from ., so the default gitdir gets found and pruned. No other ./.git exists, so everybody else (-o) is checked for type file.


Your last example:

find . -not -path ./.git -a -type f 

does not work, it just finds all files.

If you want to avoid -prune, you have to match each entry inside:

-path './.git/*'

I agree with Icarus' -name/-prune combination, but I have a modification:

]# find . -name .git -prune -o -type f -name '*git*' -print   
./.git.filehidd
./gitfile

The parens are not needed, here.

Even if -name '*git*' is added, for testing/reducing output.

The explicit action -print at the end suppresses a print by prune:

]# find . -name .git -prune                                 
./.git
./10/.git

I was unsure about the parens; I looked up man find (GNU) and found this nice detail about them:

 find . -name .snapshot -prune -o \( \! -name *~ -print0 \)

..., but omits files and directories named .snapshot (and anything in them). It also omits files or directories whose name ends in ~, but not their contents. The construct -prune -o \( ... -print0 \) is quite common. The idea here is that the expression before -prune matches things which are to be pruned. However, the -prune action itself returns true, so the following -o ensures that the right hand side is evaluated only for those directories which didn't get pruned (the contents of the pruned directories are not even visited, so their contents are irrelevant). The expression on the right hand side of the -o is in parentheses only for clarity. It emphasises that the -print0 action takes place only for things that didn't have -prune applied to them. Because the default `and' condition between tests binds more tightly than -o, this is the default anyway, but the parentheses help to show what is going on.

To me the clarity would be enhanced by rather:

find . ( -name .snapshot -prune ) -o ( ! -name '*~' -print0 )

But like that it does not work!

And if you leave out the -a like here, the -o stands out enough, so the parens can be left out, also for clarity.


(Because the shell likes parens too, you have to quote them. But then goodbye to clarity.)

1) find . -name .snapshot -prune -o \( \! -name *~ -print0 \)

2) find . -name .snapshot -prune -o ! -name '*~' -print0 

The unquoted ! can work (mine does), the unquoted * not.


So to find files (all kinds) except in any dir .git:

find -name '.git' -type d -prune -o -print 

(the type -d can also be left out, if you don't care...see Kamil's comment...it's a jungle out there, and find is a small programming language: what you need)

11
  • Thanks for your clarification.
    – SebMa
    Commented Feb 26, 2020 at 12:44
  • Do you have an answer to my last question in my EDIT2 ?
    – SebMa
    Commented Feb 26, 2020 at 12:55
  • @Paulo Tomé I don't agree with the quoting of the parenthses - from find's point of view they are unquoted, for clarity. I only add them for the shell. Man find: ( expr ) Force precedence. Since parentheses are special to the shell, you will normally need to quote them.
    – user373503
    Commented Feb 26, 2020 at 14:04
  • @rastafile Reverted the modification. Commented Feb 26, 2020 at 14:09
  • @Paulo Tomé thanks! I will add a remark about that - "clarity enhanced -- functionality zero"
    – user373503
    Commented Feb 26, 2020 at 14:38
2

The default operation to join terms in find is and so

find / -user root -name fred

means to find files which are both belonging to root and called fred. You want to do an or operation, if it it called ".git" then prune it or if it is a file then list it.

find . \( -name .git -prune \) -o -type f

Note I am using -name rather than -path. If you give it a starting location of . then the .git directory in the top level will be called ./.git for the purposes of -path.

So

find . -path .git -prune -type f

gives nothing because nothing matches -path .git as all files start ./

find . -not -path .git -type f

gives you every file because everything matches -not -path .git so the only filtering is -type f to restrict the output to files.

5
  • 2
    The explanation is right, the command can be improved. With find . \( -name .git -prune \) -o -type f there's a quirk: it will print every file (of any type, i.e. also every directory) named .git. Improvement: find . \( -type d -name .git -prune \) -o -type f -print. This prunes every .git directory (possibly somewhere deeper within .) without printing its path. If we omit -type d then it won't print a non-directory named .git (if any) somewhere in a non-excluded directory; so -type d is desired when excluding directories this way. Commented Feb 25, 2020 at 6:28
  • @icarus Thanks a lot for your answer. Can you please now read my EDIT0 and my EDIT1 ?
    – SebMa
    Commented Feb 25, 2020 at 15:24
  • I have read EDIT0. I think this is addressed by @KamilMaciorowski comment. EDIT1 just puts in the -a explicitly - I am not clear what is being asked.
    – icarus
    Commented Feb 25, 2020 at 16:53
  • @icarus I want to list all the files except those inside .git/ directory with the find command only. I'd like to understand why the last command does not work : find . -not -path ./.git -a -type f
    – SebMa
    Commented Feb 25, 2020 at 19:12
  • if you're finding in a path with many repos, youll additionally want to exclude .git directories from the output: find . \( -type d -name .git -prune -not -name .git \) -o -type f
    – marengaz
    Commented Jul 24, 2023 at 16:32

You must log in to answer this question.

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