11

My top-level directory is data. data includes several directories and these directories have sub-directories. I need to remove all files and directories inside data/ except several files in some directories.

For example, data includes the directories 100 and 101. I just want to keep a.txt and b.txt files in 100/ and c.txt and d.txt files in 101/ while removing all other files and directories in 100 and 101.

Example:

.
├── 100
│   ├── a.txt
│   ├── b.txt
│   ├── c.txt
│   └── d.txt
└── 101
    ├── a.txt
    ├── b.txt
    ├── c.txt
    └── d.txt

I use rm -rf !(a.txt|b.txt) command but I can't apply this command for each directory automatically.

12

As you already found out you can use the extglob feature which is enabled with:

shopt -s extglob

This allows to exclude matches so that you can do things like:

rm 100/!([ab].txt) 101/!([cd].txt)

It's a good idea to test it with echo first. This example will match anything inside 100/ which is not a.txt or b.txt and anything inside 101/ which is not c.txt or d.txt. If the same rules for 100/ apply to 102/ as well you can do e.g.:

10[02]/!([ab].txt) # or
{100,102}/!([ab].txt)
2
  • I just gave an example for 100 and 101 directories but in reality the number of sub-directories are nearly 20. Is there a way to implement above code for each directory without writing each directory name in the main folder? – deepblue Jan 2 '18 at 13:11
  • @deepblue Please provide an exact example for that in your question (edit), it's especially unclear how rm should know which files to keep in which directories if you don't want to specify that. – dessert Jan 2 '18 at 13:13
7

You could use find for this. You can negate tests in find with -not or !. This will exclude matches, rather than finding them.

You should be careful not to delete any of the parent directories of the files you want to keep, especially the current directory ., so make sure you read the output thoroughly before deleting.

Based on your example you could do something like this from the data directory.

find ! -path . ! -path ./100 ! -path ./101 ! -path "./100/[ab].txt" ! -path "./101/[cd].txt"

Add a ! -path ./path/to/dir for any path you want to avoid deleting. You can use metacharacters like *, but make sure you quote the expression if you do, eg "./path*dir", to prevent any unwanted expansions.

find is recursive by default. Even if we don't find ./100 here, we will find all of its contents unless they match the pattern [ab].txt. If you can't match all the names you want to keep, add another test:

! -path "./100/[ab].txt" ! -path ./100/foo

This won't find a.txt or b.txt or foo, but it will find all other files.

When you are sure you see what you want, you can add -delete to the end to delete the found files:

find ! -path . ! -path ./100 ! -path ./101 ! -path "./100/[ab].txt" ! -path "./101/[cd].txt" -delete
0

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.