HattrickNZ
HattrickNZ

Reputation: 4643

Remove all files except files with certain extension

This removes all files that end with .a or .b

$ ls *.a
a.a  b.a  c.a

$ ls *.b
a.b  b.b  c.b

$ rm *.a *.b

How would I do the opposite and remove all files that end with *.* except the ones that end with *.a and *.b?

Upvotes: 0

Views: 1578

Answers (4)

anubhava
anubhava

Reputation: 785058

You can enable extended glob in bash:

shopt -s extglob

Then you can use:

rm *.!(a|b)

To remove all files that end with *.* except the ones that end with *.a OR *.b

Update: (Thanks to @mklement0) Here is a way to localize setting extglob (without altering the global state) by doing this in a subshell and using an intermediate variable:

(shopt -s extglob; glob='*.!(a|b)'; rm $glob)

Upvotes: 1

mklement0
mklement0

Reputation: 437408

The linked answer has useful info, though the question is somewhat ambiguous and the answers use differing interpretations.

The simplest approach in your case is probably (a streamlined version of https://stackoverflow.com/a/10448940/45375):

 (GLOBIGNORE='*.a:*.b'; rm *.*)
  • Note the use of a subshell ((...)) to localize setting the GLOBIGNORE variable.
  • The patterns assigned to GLOBIGNORE must be :-separated.

The appeal of this approach is that you can use a single subshell without changing global state.

By contrast, getting away with a single subshell with shopt -s extglob requires a bit of trickery:

(shopt -s extglob; glob='*.!(a|b)'; echo $glob)

Note the mandatory use of an intermediate variable, without which the command would break (because a literal glob would be expanded BEFORE executing the commands, at which point the extended globbing syntax is not yet recognized).


Caveat: Using GLOBIGNORE has an unexpected side effect (bug?):

If GLOBIGNORE is set - to whatever value - pathname expansion of * and *.* behaves as if shell option dotglob were in effect - even if it isn't.
In other words: If GLOBIGNORE is set, hidden files not explicitly exempted by a pattern in GLOBIGNORE are always matched by * and *.*.

dotglob is OFF by default, causing * NOT to include hidden files (if GLOBIGNORE is not set, which is true by default).

If you also wanted to exclude hidden files while using GLOBIGNORE, add the following pattern: .*; applied to the question, you'd get:

 (GLOBIGNORE='*.a:*.b:.*'; rm *.*)

By contrast, using extended globbing after turning on the extglob shell option DOES respect the dotglob option.

Upvotes: 2

DevSolar
DevSolar

Reputation: 70263

Sometimes it's better to not insist on solving a problem a certain way. And for the general problem of "acting on certain files to be determined in some tricky way", find is probably the best all-around tool you'll find.

find . -type f -maxdepth 1 ! -name \*.[ab] -delete

Omit the -maxdepth 1 if you want to recurse into subdirectories.

Upvotes: 1

savanto
savanto

Reputation: 4550

There are some shells that are capable of this (I think?), however, bash is not by default. If you are running bash on Cygwin, you can do this:

rm $(ls -1 | grep -v '.*\.a' | grep -v '.*\.b')
  • ls -1 (that's a one) list all files in current directory one per line.
  • grep -v '.*\.a' return all matches that don't end in .a
  • grep -v '.*\.b' return all matches that don't end in .b

Upvotes: 0

Related Questions