234

I don't quite understand the example given from the man find, can anyone give me some examples and explanations? Can I combine regular expression in it?


The more detailed question is like this:

Write a shell script, changeall, which has an interface like changeall [-r|-R] "string1" "string2". It will find all files with an suffix of .h, .C, .cc, or .cpp and change all occurrences of string1 to string2. -r is option for staying in current dir only or including subdir's.

NOTE:

  1. For non-recursive case, ls is NOT allowed, we could only use find and sed.
  2. I tried find -depth but it was NOT supported. That's why I was wondering if -prune could help, but didn't understand the example from man find.

EDIT2: I was doing assignment, I didn't ask question in great details because I would like to finish it myself. Since I already done it and hand it in, now I can state the whole question. Also, I managed to finish the assignment without using -prune, but would like to learn it anyway.

Lii
  • 9,906
  • 6
  • 53
  • 73
derrdji
  • 10,619
  • 20
  • 61
  • 76

10 Answers10

468

The thing I'd found confusing about -prune is that it's an action (like -print), not a test (like -name). It alters the "to-do" list, but always returns true.

The general pattern for using -prune is this:

find [path] [conditions to prune] -prune -o \
            [your usual conditions] [actions to perform]

You pretty much always want the -o (logical OR) immediately after -prune, because that first part of the test (up to and including -prune) will return false for the stuff you actually want (ie: the stuff you don't want to prune out).

Here's an example:

find . -name .snapshot -prune -o -name '*.foo' -print

This will find the "*.foo" files that aren't under ".snapshot" directories. In this example, -name .snapshot makes up the [conditions to prune], and -name '*.foo' -print is [your usual conditions] and [actions to perform].

Important notes:

  1. If all you want to do is print the results you might be used to leaving out the -print action. You generally don't want to do that when using -prune.

    The default behavior of find is to "and" the entire expression with the -print action if there are no actions other than -prune (ironically) at the end. That means that writing this:

     find . -name .snapshot -prune -o -name '*.foo'              # DON'T DO THIS
    

    is equivalent to writing this:

     find . \( -name .snapshot -prune -o -name '*.foo' \) -print # DON'T DO THIS
    

    which means that it'll also print out the name of the directory you're pruning, which usually isn't what you want. Instead it's better to explicitly specify the -print action if that's what you want:

     find . -name .snapshot -prune -o -name '*.foo' -print       # DO THIS
    
  2. If your "usual condition" happens to match files that also match your prune condition, those files will not be included in the output. The way to fix this is to add a -type d predicate to your prune condition.

    For example, suppose we wanted to prune out any directory that started with .git (this is admittedly somewhat contrived -- normally you only need to remove the thing named exactly .git), but other than that wanted to see all files, including files like .gitignore. You might try this:

    find . -name '.git*' -prune -o -type f -print               # DON'T DO THIS
    

    This would not include .gitignore in the output. Here's the fixed version:

    find . -name '.git*' -type d -prune -o -type f -print       # DO THIS
    

Extra tip: if you're using the GNU version of find, the texinfo page for find has a more detailed explanation than its manpage (as is true for most GNU utilities).

Jerzy Brzóska
  • 347
  • 1
  • 13
Laurence Gonsalves
  • 125,464
  • 31
  • 220
  • 273
  • 7
    it's not 100% obvious in your text (but as you only print '*.foo' it doesn't conflict) but the -prune part will also not print anything (not only directories) named ".snapshot". ie, `-prune` doesn't only work on directories (but, for directories, it also does prevent entering the directories matching that condition, ie here the dirs matching that `-name .snapshot`). – Olivier Dulac Jan 16 '13 at 16:22
  • 13
    and +1 for you for the nicely done explanation (and especially the important note). You should submit this to the find developpers (as the man page doesn't explain "prune" for normal human beings ^^ It took me many tries to figure it out, and I didn't see that side effect you warns us about) – Olivier Dulac Jan 16 '13 at 16:25
  • 3
    @OlivierDulac That's a very good point about potentially stripping files you want to keep. I've updated the answer to clarify this. it isn't actually `-prune` itself that causes this, by the way. The issue is that the or operator "short-circuits", and or has lower precedence than and. The end result is that if a file called `.snapshot` is encountered it will match the first `-name`, `-prune` will then do nothing (but return true), and then the or returns true since its left-argument was true. The action (eg: `-print`) is part of its second argument, so it never has a chance to execute. – Laurence Gonsalves Jan 19 '13 at 02:44
  • 3
    +1 finally found out why I need `-print` at end, I can now stop adding `\! -path ` in addition to `-prune` – Miserable Variable May 14 '13 at 20:39
  • 6
    Note that "-o" is shorthand for "-or", which (while not POSIX-compliant) reads more clearly. – yoyo Aug 14 '14 at 22:24
  • Beware that spaces matter for some reason. I'm no expert, but I found if I left an extra space character before the -name in the above example, it included the pruned directory names, much like what is warned about if you don't put "-print" at the end. I'm guessing it interpreted some kind of indentation structure, when it was just an accidental extra space character which usually means nothing special on a bare bash command line. – Starman Nov 12 '18 at 20:55
  • 1
    @Starman can you post the command you're talking about? Maybe link to a gist. I can't think of any way spaces would behave the way you describe in `find`. – Laurence Gonsalves Nov 12 '18 at 21:13
  • 1
    Great answer. Unfortunately, I'll probably be coming back to it for the rest of my life. The entire syntax of `-prune` and maybe even `find` as a whole might be GNU's biggest abomination. – Hashim Aziz Oct 20 '19 at 18:34
  • The point about the final `-print`, is this POSIX or Linux specifics? – Hibou57 Apr 27 '20 at 13:14
32

Normally the native way we do things in linux and the way we think is from left to right.
So you would go and write what you are looking for first:

find / -name "*.php"

Then you probably hit enter and realize you are getting too many files from directories you wish not to. Let's exclude /media to avoid searching your mounted drives.
You should now just APPEND the following to the previous command:

-print -o -path '/media' -prune

so the final command is:

find / -name "*.php" -print -o -path '/media' -prune

...............|<--- Include --->|....................|<---------- Exclude --------->|

I think this structure is much easier and correlates to the right approach

AmitP
  • 4,985
  • 4
  • 31
  • 27
  • 4
    I would not have expected this to be efficient - I would have thought that it would evaluate the left clause first before the prune, but to my surprise a quick test seems to suggest that `find` is clever enough to process the `-prune` clause first. Hmmm, interesting. – artfulrobot Apr 29 '14 at 11:08
  • I had never considered it that in nearly a decade of using GNU find! Thank you for that! It will definitely change the way I think about `-prune` from now on. – Felipe Alvarez Sep 01 '16 at 05:56
  • 6
    @artfulrobot Is it really processing it first? I would have thought it's entering `/media`, noticing that it's not called `*.php` and then checking whether it is currently inside `/media`, seeing that it is and therefore skipping that whole subtree. It's still left-to-right, it just makes no difference as long as both checks don't overlap. – phk Nov 20 '16 at 00:35
  • 1
    I could not get this order to work with `-exec cp {}`. Probably missing something in the syntax. – Rajib Oct 03 '20 at 04:24
  • Very nice explanation. – Robert May 25 '21 at 06:52
26

Beware that -prune does not prevent descending into any directory as some have said. It prevents descending into directories that match the test it's applied to. Perhaps some examples will help (see the bottom for a regex example). Sorry for this being so lengthy.

$ find . -printf "%y %p\n"    # print the file type the first time FYI
d .
f ./test
d ./dir1
d ./dir1/test
f ./dir1/test/file
f ./dir1/test/test
d ./dir1/scripts
f ./dir1/scripts/myscript.pl
f ./dir1/scripts/myscript.sh
f ./dir1/scripts/myscript.py
d ./dir2
d ./dir2/test
f ./dir2/test/file
f ./dir2/test/myscript.pl
f ./dir2/test/myscript.sh

$ find . -name test
./test
./dir1/test
./dir1/test/test
./dir2/test

$ find . -prune
.

$ find . -name test -prune
./test
./dir1/test
./dir2/test

$ find . -name test -prune -o -print
.
./dir1
./dir1/scripts
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.sh
./dir1/scripts/myscript.py
./dir2

$ find . -regex ".*/my.*p.$"
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.py
./dir2/test/myscript.pl

$ find . -name test -prune -regex ".*/my.*p.$"
(no results)

$ find . -name test -prune -o -regex ".*/my.*p.$"
./test
./dir1/test
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.py
./dir2/test

$ find . -regex ".*/my.*p.$" -a -not -regex ".*test.*"
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.py

$ find . -not -regex ".*test.*"                   .
./dir1
./dir1/scripts
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.sh
./dir1/scripts/myscript.py
./dir2
Dennis Williamson
  • 303,596
  • 86
  • 357
  • 418
  • if you also "touch ./dir1/scripts/test" (ie, have a "test" file, and not dir, in that printed out subdir), it won't get printd by the `find . -name test -prune -o -print` : iow, `-prune` is an action that also works on files – Olivier Dulac Jan 16 '13 at 16:27
  • this can IMHO be somehow misleading, because `-prune` used in conjunction with `-path` (instead of `-name`) **does** prevent descending into it. - anyhow, this (with the first comment) exemplifies nicely how `-prune` works and that it is an action and no test. – DJCrashdummy Mar 10 '21 at 06:43
11

Adding to the advice given in other answers (I have no rep to create replies)...

When combining -prune with other expressions, there is a subtle difference in behavior depending on which other expressions are used.

@Laurence Gonsalves' example will find the "*.foo" files that aren't under ".snapshot" directories:-

find . -name .snapshot -prune -o -name '*.foo' -print

However, this slightly different short-hand will, perhaps inadvertently, also list the .snapshot directory (and any nested .snapshot directories):-

find . -name .snapshot -prune -o -name '*.foo'

The reason is (according to the manpage on my system):-

If the given expression does not contain any of the primaries -exec, -ls, -ok, or -print, the given expression is effectively replaced by:

( given_expression ) -print

That is, the second example is the equivalent of entering the following, thereby modifying the grouping of terms:-

find . \( -name .snapshot -prune -o -name '*.foo' \) -print

This has at least been seen on Solaris 5.10. Having used various flavors of *nix for approx 10 years, I've only recently searched for a reason why this occurs.

crw
  • 565
  • 6
  • 17
  • 1
    Thank you for calling attention to the difference between using `-prune` with and without `-print`! – mcw Dec 01 '19 at 04:06
3

Prune is a do not recurse at any directory switch.

From the man page

If -depth is not given, true; if the file is a directory, do not descend into it. If -depth is given, false; no effect.

Basically it will not desend into any sub directories.

Take this example:

You have the following directories

  • /home/test2
  • /home/test2/test2

If you run find -name test2:

It will return both directories

If you run find -name test2 -prune:

It will return only /home/test2 as it will not descend into /home/test2 to find /home/test2/test2

AdamW
  • 1,063
  • 10
  • 14
  • 1
    not 100% true: it is "do pruning when matching condition, and if it's a directory, take it out of the to-do list, ie don't enter it as well". -prune also works on files. – Olivier Dulac Jan 16 '13 at 16:29
3

I am no expert at this (and this page was very helpful along with http://mywiki.wooledge.org/UsingFind)

Just noticed -path is for a path that fully matches the string/path that comes just after find (. in theses examples) where as -name matches all basenames.

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

blocks the .git directory in your current directory ( as your finding in . )

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

blocks all .git subdirectories recursively.

Note the ./ is extremely important!! -path must match a path anchored to . or whatever comes just after find if you get matches with out it (from the other side of the or '-o') there probably not being pruned! I was naively unaware of this and it put me of using -path when it is great when you don't want to prune all subdirectory with the same basename :D

sabgenton
  • 1,563
  • 9
  • 19
  • Note if your doing say `find bla/` then you would need -path `bla/`.git (or if you shoved a `*` at the front instead it would behave more like -name) – sabgenton Dec 05 '13 at 09:11
2

find builds a list of files. It applies the predicate you supplied to each one and returns those that pass.

This idea that -prune means exclude from results was really confusing for me. You can exclude a file without prune:

find -name 'bad_guy' -o -name 'good_guy' -print  // good_guy

All -prune does is alter the behavior of the search. If the current match is a directory, it says "hey find, that file you just matched, dont descend into it". It just removes that tree (but not the file itself) from the list of files to search.

It should be named -dont-descend.

seeker_of_bacon
  • 1,188
  • 1
  • 13
  • 17
  • that makes no sense, because also other tests than `-path` can be used before `-prune`. - IMHO it makes things more clear, if you are aware of, that `-prune` is an action like `-print`. if you really want to rename it, i'd rather suggest `-dontprint`, `-forget` or something similar. ;-) – DJCrashdummy Mar 10 '21 at 06:12
1

If you read all the good answers here my understanding now is that the following all return the same results:

find . -path ./dir1\*  -prune -o -print

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

find . -path ./dir1\*  -o -print
#look no prune at all!

But the last one will take a lot longer as it still searches out everything in dir1. I guess the real question is how to -or out unwanted results without actually searching them.

So I guess prune means don't decent past matches but mark it as done...

http://www.gnu.org/software/findutils/manual/html_mono/find.html "This however is not due to the effect of the ‘-prune’ action (which only prevents further descent, it doesn't make sure we ignore that item). Instead, this effect is due to the use of ‘-o’. Since the left hand side of the “or” condition has succeeded for ./src/emacs, it is not necessary to evaluate the right-hand-side (‘-print’) at all for this particular file."

sabgenton
  • 1,563
  • 9
  • 19
1

Show everything including dir itself but not its long boring contents:

find . -print -name dir -prune
devon
  • 674
  • 5
  • 15
0

There are quite a few answers; some of them are a bit too much theory-heavy. I'll leave why I needed prune once so maybe the need-first/example kind of explanation is useful to someone :)

Problem

I had a folder with about 20 node directories, each having its node_modules directory as expected.

Once you get into any project, you see each ../node_modules/module. But you know how it is. Almost every module has dependencies, so what you are looking at is more like projectN/node_modules/moduleX/node_modules/moduleZ...

I didn't want to drown with a list with the dependency of the dependency of...

Knowing -d n / -depth n, it wouldn't have helped me, as the main/first node_modules directory I wanted of each project was at a different depth, like this:

Projects/MysuperProjectName/project/node_modules/...
Projects/Whatshisname/version3/project/node_modules/...
Projects/project/node_modules/...
Projects/MysuperProjectName/testProject/november2015Copy/project/node_modules/...
[...]

How can I get the first a list of paths ending at the first node_modules and move to the next project to get the same?

Enter -prune

When you add -prune, you'll still have a standard recursive search. Each "path" is analyzed, and every find gets spit out and find keeps digging down like a good chap. But it's the digging down for more node_modules what I didn't want.

So, the difference is that in any of those different paths, -prune will find to stop digging further down that particular avenue when it has found your item. In my case, the node_modules folder.

Community
  • 1
  • 1
Carles Alcolea
  • 7,984
  • 4
  • 30
  • 51