12

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 ?

  • 6
    In Unix and Linux, we call these "directories", not "folders". – tchrist Feb 7 '18 at 2:34
  • 1
    Depending on your shell, you may need to quote that ! operator: \! or '!'. – Toby Speight Feb 7 '18 at 8:32
25

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
  • 1
    Alternative to -prune to leave test2/*/ subdirectories alone: return to rm -r and add -maxdepth 1. – Toby Speight Feb 7 '18 at 8:35
  • @Isaac OK. Done. (Also, +1 for your excellent answer.) – John1024 Nov 9 '18 at 8:30
  • Excellent work!, but sorry: that will remove all files inside ./myfolder. You need a missing (IMvhO) -type d for only directories. – Isaac Nov 9 '18 at 9:06
  • Ok, this should work as you want: find ./myfolder -depth -mindepth 1 -maxdepth 1 -type d ! -regex '^./myfolder/test2\(/.*\)?' – Isaac Nov 9 '18 at 10:25
10

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
4

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
2

If you're using zsh, then you could:

setopt extended_glob # if you don't have it enabled

rm -rf myfolder/^test2
0

Tested with below command and it worked fine

find  /home/myfolder -maxdepth 1 -type d ! -iname test2 -exec rm -rvf {} \;
  • 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 8 '18 at 20:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.