0

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?

savanto
  • 4,470
  • 23
  • 40
HattrickNZ
  • 4,373
  • 15
  • 54
  • 98
  • 1
    possible duplicate of [rm all files except some](http://stackoverflow.com/questions/4325216/rm-all-files-except-some) – savanto Jun 05 '14 at 01:49
  • similar [here](http://stackoverflow.com/questions/216995/how-can-i-use-inverse-or-negative-wildcards-when-pattern-matching-in-a-unix-linu) use `shopt -u extglob` to disable – HattrickNZ Jun 11 '14 at 02:22

4 Answers4

2

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.

Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775
1

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)
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • 2
    Inspired by a comment by @savanto, I figured out a way to _localize_ setting `extglob` (i.e., to not alter the global state) by way of a subshell: `(shopt -s extglob; glob='*.!(a|b)'; rm $glob)` - note the mandatory use of an intermediate variable. – mklement0 Jun 05 '14 at 06:29
1

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.

DevSolar
  • 67,862
  • 21
  • 134
  • 209
0

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
savanto
  • 4,470
  • 23
  • 40
  • tks, was hoping to use a variable so that i could add other criteria not to be deleted. – HattrickNZ Jun 05 '14 at 02:31
  • @HattrickNZ See [this answer](http://stackoverflow.com/a/15232634/3565972) from another post. Your bash may support this style syntax, with `extglob` enabled: `rm !(*.a|*.b)` in which case you may be able to use a variable. – savanto Jun 05 '14 at 02:36
  • @HattrickNZ In fact, I can verify that this works: `shopt -s extglob; rm_all_but="*.a|*.b"; rm !($rm_all_but)`. Keep in mind that `extglob` will be left enabled... – savanto Jun 05 '14 at 02:38
  • i'll have to look up `extglob` – HattrickNZ Jun 05 '14 at 03:55
  • 1
    Thanks for figuring out the intermediate-variable technique to allow putting `shopt -s extglob` and use of an extended glob on the same line. If you stick this in a _subshell_, setting `extglob` is effectively _localized_ (isn't left enabled): `(shopt -s extglob; glob='!(*.a|*.b)'; rm $glob)`. – mklement0 Jun 05 '14 at 06:25
  • @mklement0 That's cool! But is there any reason **not** to leave extglob enabled? It's disabled by default, so I left it like that. Will it get in my way? – savanto Jun 05 '14 at 07:00
  • Generally I'd say it's problematic to change _global_ state just to support a single operation. Code executed later may make the assumption that the global state was _not_ changed. In this particular case, leaving `extglob` on causes more strings to be valid globs, so - at least hypothetically - something that's considered a string literal with `extglob` OFF could unexpectedly turn into a pattern with it ON. – mklement0 Jun 05 '14 at 07:14