50

I need to delete all folders inside a folder using a daily script. The folder for that day needs to be left.

Folder 'myfolder' has 3 sub folder: 'test1', 'test2' and 'test3' I need to delete all except 'test2'.

I am trying to match exact name here:

find /home/myfolder -type d ! -name 'test2' | xargs rm -rf

OR

find /home/myfolder -type d ! -name 'test2' -delete

This command always tries to delete the main folder 'myfolder' also ! Is there a way to avoid this ?

Riju Mahna
  • 633
  • 1
  • 5
  • 6

6 Answers6

65

This will delete all folders inside ./myfolder except that ./myfolder/test2 and all its contents will be preserved:

find ./myfolder -mindepth 1 ! -regex '^./myfolder/test2\(/.*\)?' -delete

How it works

  • find starts a find command.
  • ./myfolder tells find to start with the directory ./myfolder and its contents.

  • -mindepth 1 not to match ./myfolder itself, just the files and directories under it.

  • ! -regex '^./myfolder/test2\(/.*\)?' tells find to exclude (!) any file or directory matching the regular expression ^./myfolder/test2\(/.*\)?. ^ matches the start of the path name. The expression (/.*\)? matches either (a) a slash followed by anything or (b) nothing at all.

  • -delete tells find to delete the matching (that is, non-excluded) files.

Example

Consider a directory structure that looks like;

$ find ./myfolder
./myfolder
./myfolder/test1
./myfolder/test1/dir1
./myfolder/test1/dir1/test2
./myfolder/test1/dir1/test2/file4
./myfolder/test1/file1
./myfolder/test3
./myfolder/test3/file3
./myfolder/test2
./myfolder/test2/file2
./myfolder/test2/dir2

We can run the find command (without -delete) to see what it matches:

$ find ./myfolder -mindepth 1 ! -regex '^./myfolder/test2\(/.*\)?'
./myfolder/test1
./myfolder/test1/dir1
./myfolder/test1/dir1/test2
./myfolder/test1/dir1/test2/file4
./myfolder/test1/file1
./myfolder/test3
./myfolder/test3/file3

We can verify that this worked by looking at the files which remain:

$ find ./myfolder
./myfolder
./myfolder/test2
./myfolder/test2/file2
./myfolder/test2/dir2
John1024
  • 73,527
  • 11
  • 167
  • 163
  • 1
    Alternative to `-prune` to leave `test2/*/` subdirectories alone: return to `rm -r` and add `-maxdepth 1`. – Toby Speight Feb 07 '18 at 08:35
  • @Isaac OK. Done. (Also, +1 for your excellent answer.) – John1024 Nov 09 '18 at 08:30
  • 1
    Excellent work!, but sorry: that will remove all **files** inside `./myfolder`. You need a missing (IMvhO) `-type d` for **only directories**. –  Nov 09 '18 at 09:06
  • 1
    Ok, this should work as you want: `find ./myfolder -depth -mindepth 1 -maxdepth 1 -type d ! -regex '^./myfolder/test2\(/.*\)?'` –  Nov 09 '18 at 10:25
17

Using bash:

shopt -s extglob
rm -r myfolder/!(test2)/

Example:

$ tree myfolder/
myfolder/
├── test1
│   └── file1
├── test2
│   └── file2
└── test3
    └── file3

$ echo rm -r myfolder/!(test2)
rm -r myfolder/test1 myfolder/test3
$ rm -r myfolder/!(test2)
$ tree myfolder/
myfolder/
└── test2
    └── file2

1 directory, 1 file
Jeff Schaller
  • 66,199
  • 35
  • 114
  • 250
12

tl;dr

find ./myfolder -mindepth 1 -maxdepth 1 -type d -not -name test2 \
     -exec echo rm -rf '{}' \;

Remove echo if satisfied with the list of files.


Using -mindepth 1 will ensure that the top directory is not selected.

$ find ./myfolder -mindepth 1 -type d
./myfolder/test2
./myfolder/test2/one
./myfolder/test2/two
./myfolder/test
./myfolder/test/a1
./myfolder/test/a1/a2
./myfolder/test/a1/a2/a3

But a -not -name test2 will not avoid subdirs inside test2:

$ find ./myfolder -mindepth 1 -type d -not -name 'test2'
./myfolder/test2/one
./myfolder/test2/two
./myfolder/test
./myfolder/test/a1
./myfolder/test/a1/a2
./myfolder/test/a1/a2/a3

To do that, you need something like prune:

$ find ./myfolder -mindepth 1 -name test2 -prune -o -type d -print
./myfolder/test
./myfolder/test/a1
./myfolder/test/a1/a2
./myfolder/test/a1/a2/a3

But do not use delete, as it implies depth and that will start erasing from the longest path:

$ find ./myfolder -depth -mindepth 1 -name test2 -prune -o -type d -print
./myfolder/test/a1/a2/a3
./myfolder/test/a1/a2
./myfolder/test/a1
./myfolder/test

Either use rm -rf (remove the echo if you want to actually erase):

$ find ./myfolder -mindepth 1 -name test2 -prune -o -type d -exec echo rm -rf '{}' \;
rm -rf ./myfolder/test
rm -rf ./myfolder/test/a1
rm -rf ./myfolder/test/a1/a2
rm -rf ./myfolder/test/a1/a2/a3

Or, also use maxdepth if all you need is to delete directories (and everything inside) (remove the echo to actually erase):

$ find ./myfolder -mindepth 1 -maxdepth 1 -type d -not -name test2 -exec echo rm -rf '{}' \;
rm -rf ./myfolder/test

A -delete will still fail if the directory is not empty:

$ find ./myfolder -mindepth 1 -maxdepth 1 -type d -not -name test2 -delete
find: cannot delete ‘./myfolder/test’: Directory not empty
4

If you're using zsh, then you could:

setopt extended_glob # if you don't have it enabled

rm -rf myfolder/^test2(/)

^foo is a glob meaning everything except foo, and (/) is a glob qualifier that says that the glob should only match directories.

JoL
  • 4,520
  • 15
  • 35
3

If the requirement is only for level one (not more than depth 1) as mentioned in the question, here is another easy option:

ls myfolder/ | grep -v "test2" | xargs rm -r

Details:

  • ls myfolder/ lists all folders and files inside myfolder/ directory
  • grep -v "test2" matches everything except "test2"
  • xargs rm -r pipes the output from previous command to rm -r command.
jackberry
  • 131
  • 1
  • And with recursive delete of all sub folders find myfolder/* -type d | grep -v "test2" | xargs rm -rf – Braikar Apr 23 '21 at 09:12
0

Tested with below command and it worked fine

find  /home/myfolder -maxdepth 1 -type d ! -iname test2 -exec rm -rvf {} \;
Praveen Kumar BS
  • 5,139
  • 2
  • 9
  • 14
  • 2
    You've run into the same problem as the OP; listing /home/folder on the command-line (without the critical `-mindepth 1`) makes that top directory match all the criteria (it's a directory and it's not named "test2") and so it gets deleted. – Jeff Schaller Nov 08 '18 at 20:21