239

I issue the following command to find the .svn directories:

find . -name ".svn"

That gives me the following results:

./toto/.svn
./toto/titi/.svn
./toto/tata/.svn

How could I process all these lines with rm -fr in order to delete the directories and their content?

Arnaud
  • 2,501
  • 2
  • 12
  • 8

9 Answers9

290

Find can execute arguments with the -exec option for each match it finds. It is a recommended mechanism because you can handle paths with spaces/newlines and other characters in them correctly. You will have to delete the contents of the directory before you can remove the directory itself, so use -r with the rm command to achieve this.

For your example you can issue:

find . -name ".svn" -exec rm -r "{}" \;

You can also tell find to just find directories named .svn by adding a -type d check:

find . -name ".svn" -type d -exec rm -r "{}" \;

Warning Use rm -r with caution it deletes the folder and all its contents.

If you want to delete just empty directories as well as directories that contain only empty directories, find can do that itself with -delete and -empty:

find . -name ".svn" -type d -empty -delete
Mikkel
  • 526
  • 6
  • 14
Drav Sloan
  • 14,145
  • 4
  • 45
  • 43
  • 39
    I have seen advice to always run `-type` *after* `-name` in find commands, since calls to `stat` to get the type are expensive. I just tried it myself on a fairly large bunch of files, and it seems to be true: running `find . -name 'foo' -type d` took 19 secs, while `find . -type d -name 'foo'` took 32 secs. So about 50% longer time to run `-type` first. – SpinUp __ A Davis Mar 16 '15 at 16:37
  • 14
    i've been using this command for years, but on Mac now i get errors saying those directories do not exist. Even though it does delete them. I never saw messages before. – chovy Dec 23 '15 at 08:51
  • 2
    Same as @chovy. Is there a way to get rid of these messages? – Clément Apr 23 '16 at 15:32
  • 4
    @chovy @clément That is because `find` wants to see in that folder for other matches, while it's removing the folder at the same time. ~I don't know yet how to fix this.~ Dirty fix: `find . -name "folder-to-delete" -print0 | xargs -r0 -- rm -r` – Charlie Jul 20 '16 at 12:02
  • 9
    @chovy @Clément I think the `-depth` argument fixes this: `find . -depth -name ".svn" -type d -exec rm -r "{}" \;` – gimboland Jul 27 '16 at 07:57
  • 1
    I've submitted an update to recommend `-delete` when removing only empty directories. – Mikkel Jul 27 '16 at 21:34
  • is there a way to negate this? I want to delete all folders that does not contain a certain file... – Reigel Gallarde Sep 26 '16 at 09:57
  • @Reigel yes. Use find's "-not" operator. e.g. 'find . -not -name ".svn"' – gaoithe Mar 22 '17 at 12:07
  • This started to delete all my files from my harddisk and I cannot recover them – alper Feb 04 '21 at 14:46
  • When `find` finds both a directory and a subdirectory, it deletes the parent directory first, so then `rm` throws an error trying to delete the child. I can add `-f` to `rm` to fix this. But is there a smarter way? (e.g. so I don't accidentally delete files that `rm` usually rejects based on permissions) – falsePockets Jul 23 '21 at 00:50
98

Here is a portable still faster than the accepted answer way.

Using a + instead of a semicolon as find command terminator is optimizing the CPU usage. That can be significant if you have a lot of .svn sub-directories:

find . -name .svn -type d -exec rm -rf {} +

Note also that you never1 need to quote the curly braces here.

1 Unless you use the fish shell (this might have been fixed since I wrote this reply).

jlliagre
  • 60,319
  • 10
  • 115
  • 157
  • 6
    What's difference between + and semicolon? Why we don't use curly braces? – Shicheng Guo Jan 04 '17 at 19:25
  • 3
    @ShichengGuo With the semicolon, there will be one rm command per directory found, with the + a single rm command will process all directories found (or at least a very large number of them.) I don't get your second question, curly braces are used here. – jlliagre Jan 04 '17 at 20:13
  • I think @ShichengGuo means *why don't we need to quote the curly braces here* (@jlliagre wrote that we never need to quote them). I can't find a reference now, but I understand it's because find will automatically escape the paths replaced for {}. – Quinn Comendant Mar 08 '17 at 06:47
  • The answer and the question are not quite right on what + does. If many files are found then ';' would give 'command line too long' error. + splits the files found in batches which are less than max allowed command line length and runs the command for each batch. – gaoithe Mar 22 '17 at 11:56
  • 2
    @gaoithe I did rollback you edit which replaced a correct statement with an incorrect one. Using `+` **does** reduce CPU usage, Using `;` **does not** lead to a command too long error. – jlliagre Mar 22 '17 at 12:26
  • @QuinnComendant That's a way to understand it but `find` won't actually escape the path replaced for `{}` because there is no need to do it. It simply passes the file name(s) as is to the executed command, here `rm`. – jlliagre Mar 22 '17 at 12:43
  • @jlliagre okay, but sorry not correct. Using -delete would be best on CPU: No sub processes spawned to do rm. Using ; would be next best: just one rm sub-process spawned BUT could fail due to command line too long. Using + is the best overall as it is portable BUT it results in one sub-process spawned per batch of files processed so it is actually a bit heavier on CPU. – gaoithe Mar 22 '17 at 13:10
  • @jlliagre test using a find command where a huge number of files are found. ; will give command line too long errors. – gaoithe Mar 22 '17 at 13:13
  • Try using: 'strace -f -e trace=process find . -print' and 'strace -f -e trace=process find . -exec echo +' and 'strace -f -e trace=process find . -exec echo ';''. Wow, that is great fun. Okay, yes I was wrong in my last comment here about ';', but I do stand by the edit. ';' is very heavy on CPU many many processes spawned. You don't get command-line too long with a plain find BUT if you pipe output of find to another command. 'find | ' will get errors if ';' or -print is used and there are many files. But with + the files are batched up so no error. – gaoithe Mar 22 '17 at 13:30
  • @gaoithe Your last comment kind of states the opposite than you previous one. You now correctly states `+` is lighter than `;` which is precisely my point in this reply. Piping `find` output to something else is off topic, especially because `+` was design to avoid doing it. Moreover, suggesting to use `-delete` while my answer was aimed to provide a portable solution was ruining it, not to mention it wouldn't have worked in the OP case. – jlliagre Mar 22 '17 at 14:25
  • For me `fish, version 3.0.2` does not require to quote `{}` – kyb Feb 14 '20 at 10:36
  • @kyb Thanks, `fish` might have changed its behavior in the recent years. Updating my reply. – jlliagre Feb 14 '20 at 12:36
  • 1
    This should definitely be the accepted answer! Working in 2022 in bash. – Don Charlie Oct 08 '22 at 05:51
33

Assume you are using gnu find, you can use the -delete option:

find . -name test -delete

which is easier to remember.

Greg
  • 121
  • 4
Chun Yang
  • 487
  • 4
  • 4
  • Consider expanding your post with an explanation of the command (or documentation to back up your solution). Often one (or two) line answers are not the most illuminating. – HalosGhost Aug 10 '14 at 23:02
  • 146
    This doesn't work on non-empty directories. – belacqua Dec 16 '14 at 20:47
  • 2
    also works on Mac OS X – draw Mar 29 '16 at 03:11
  • This does not work for non-empty folder but it's the easier & safer solution – alexandre-rousseau Feb 07 '18 at 16:01
  • 3
    The options order is very important, find will execute them in order so, -delete have to be the last one – Jose Ignacio Centeno Mar 06 '18 at 07:25
  • `-delete` *can* be used with directories but will only delete empty directories. You must therefore make sure your expression selects all the files inside the directory in addition to the directory itself (note that `-delete` implies `-depth`). This will work: `find . -regextype posix-egrep -regex '.*/test(/.*)?' -delete` – Robin A. Meade Jun 16 '22 at 02:10
18

A faster way to do this is:

find . -name ".svn" -type d -prune -exec rm -rf '{}' '+'

In case you have ".svn" inside another ".svn".

Johann Chang
  • 291
  • 2
  • 6
15

On my computer when I use:

find . \( -name dirname -type d \) -exec rm -r '{}' ';'

The directories are deleted but I get the error:

find: ‘./dirname’: No such file or directory

for each directory.

My directories aren't empty, so the -delete option won't work for me. I found the reason for this behavior here:

  1. find takes (not necessarily) the first entry in the ./ directory. this would be e.g. dir.1/
  2. it compares it to the pattern 'dir.?'. does it match? yes.
  3. find executes "rm -r dir.1".
  4. find tries to enter dir.1/ to find the pattern within the directory. it doesn't know anything about the exec command.
  5. it doesn't find dir.1/ anymore. returns ENOENT (look at the strace output)

I used this instead to work around:

rm -r `find . -name dirname -type d`

Keep in mind that find will still try to recurse into directories named dirname, which isn't really necessary and will take some additional time. Depending on your directory structure, you might be able to work around this with the --depth find option. In addition, if you have a directory structure like dirname/foo/dirname you will get "No such file or directory" errors from rm. To suppress the errors you can redirect stderr to /dev/null or use the -f (force) flag with rm.

Samuel
  • 267
  • 2
  • 3
7

I've found that the -delete action does work nicely with the -path test. For instance, the following ought to work on the original posters problem:

find . -path '*/.svn*' -delete

Note that in addition to deleting '.svn' directories (and their contents), this will also delete any files or directories whose names start with '.svn'. For example, if you used -path '*/.git*' it would also delete '.gitignore' and '.gitattribute' in addition to '.git/'. To avoid that, and just delete directories with that exact name use:

find . -path '*/.svn/*' -or -name '.svn' -delete

Note the slash after .svn. This will first find, and delete, all the files under '.svn', then delete the .svn directory itself.

pavon
  • 239
  • 1
  • 10
Magnus
  • 343
  • 1
  • 3
  • 8
  • Are you sure? `-delete` implies `-depth`, and it sure deletes non-empty directories on my system. – Magnus Dec 20 '17 at 13:56
  • Retested and it works. Maybe it was just typo which I've corrected. – kenorb Jan 05 '18 at 22:23
  • I also tried this approach and it worked - the directories were deleted. – A.L. Apr 06 '18 at 08:38
  • I’m positive that, for me, `-delete` won’t delete non-empty directories (GNU find 4.6.0). Seems to be the case for many people. What is your implementation of `find`? – Maëlan Dec 10 '21 at 01:13
7

Bash specific solution:

shopt -s globstar
rm -r **/.svn
shopt -u globstar #optional. this will disable globstar expansion
rush
  • 27,055
  • 7
  • 87
  • 112
  • 4
    Note for command line expansion of globs that match many files, there is a limit to the number of files you can match with this mechanism. Going over this limit will result in `bash: /bin/rm: Argument list too long` – Drav Sloan Sep 09 '13 at 10:06
  • 1
    @DravSloan is correct, but that limit is in the hundreds of thousands of files. It's something to bear in mind, but probably won't be a problem for most people. – evilsoup Sep 09 '13 at 10:26
3

TLDR

find . -name .svn -prune -execdir rm -rf {} +

Details

Use -execdir, not -exec

From man find:

There are unavoidable security problems surrounding use of the -exec action; you should use the -execdir option instead.

In most case, -execdir is a drop-in replacement for -exec.

Use +, not ;

From man find:

As with the -exec action, the `+' form of -execdir will build a command line to process more than one matched file, but any given invocation of command will only list files that exist in the same subdirectory.

When looking for an exact name match, + and ; will do the same, as you cannot have two files with the same name in the same directory, but + will provide increased performance when several files/directories match your find expression within the same directory.

Also, ; needs escaping from your shell, + does not.

Use -prune

From man find:

-prune: True; if the file is a directory, do not descend into it.

This avoid searching a directory that we want to delete. Obviously, it needs to be put after the name test. See:

$ mkdir -p test/foo/bar
$ find test -name foo -execdir rm -rf {} +
find: ‘test/foo/bar’: No such file or directory

Versus:

$ mkdir -p test/foo/bar
$ find test -name foo -prune -execdir rm -rf {} +
# no error
  • 1
    I’d use `-prune` rather than `-depth`. There is no point in having `find` recurse under directories you are going to delete anyway. – Maëlan Dec 10 '21 at 01:17
  • 1
    Using `-execdir` (rather than `-exec`) cancels all the benefit of using `+` (rather than `;`). From the man page again: “_As with the `-exec` action, the `+` form of `-execdir` will build a command line to process more than one matched file, but any given invocation of `command` will only list files that exist in the same subdirectory._” In our case, two occurrences can never lie in the same parent directory, since both occurrences have the same base name. Security is not a concern for a command line that you type yourself in a terminal once, with a known command being `-exec`uted. – Maëlan Dec 10 '21 at 01:34
1

Short and simplest solution is to use xargs here

find . -name ".svn" | xargs rm -rf
Jeegar Patel
  • 109
  • 3