131

I need to delete all files in a directory, but exclude some of them. For example, in a directory with the files a b c ... z, I need to delete all except for u and p. Is there an easy way to do this?

2
  • The answers below are a lot better, but you could just make the files to save read-only, delete all, and then change them back to their original permissions (as long as you don't use rm -f). You'd have to know what permissions to restore and you'd have to know that nothing needed write access to them during the process. This is why the other answers are better.
    – Joe
    Jan 15, 2013 at 5:13
  • 1
    If you also want to delete hidden files run shopt -s dotglob before running rm (...).
    – user185585
    Jan 28, 2013 at 8:07

17 Answers 17

111

What I do in those cases is to type

rm *

Then I press Ctrl+X,* to expand * into all visible file names.

Then I can just remove the two files I like to keep from the list and finally execute the command line.

9
  • 25
    I guess this works only as long as the list of files which * expands too isn't getting too long. :-} Jan 8, 2013 at 16:57
  • 11
    Esc followed by * will also expand the "*".
    – slowpoison
    Jan 9, 2013 at 20:45
  • 2
    @SantoshKumar: That doesn't make sense to me. The expansion will always work, it doesn't depend on what command you want to use afterwards. Jan 17, 2013 at 18:18
  • 2
    @OliverSalzburg Sorry, the combination is little bit confusing. I think you should write like Ctrl + Shift + x + * Jan 18, 2013 at 5:22
  • 3
    @SantoshKumar: But that's not the combination. Jan 18, 2013 at 11:26
146

To rm all but u,p in bash just type:

rm !(u|p)

This requires the following option to be set:

shopt -s extglob

See more: glob - Greg's Wiki

3
  • 1
    you must have 'extglobbing' active: shopt -s extglob
    – sparkie
    Jan 8, 2013 at 13:07
  • 18
    You need to shopt -s extglob, @Ashot. Also, it's just files, not directories, which is why I've removed the -rf options in your command.
    – slhck
    Jan 8, 2013 at 13:07
  • 4
    If you need to exclude one file of a selection of files, try this: rm !(index).html. This will delete all files ending in ".html" with the exception of "index.html".
    – mzuther
    Jul 24, 2015 at 21:46
82

You can use find

find . ! -name u ! -name p -maxdepth 1 -type f -delete
  • ! negates the next expression
  • -name specifies a filename
  • -maxdepth 1 will make find process the specified directory only (find by default traverses directories)
  • -type f will process only files (and not for example directories)
  • -delete will delete the files

You can then tune the conditions looking at the man page of find

Update

  • Keep in mind that the order of the elements of the expressions is significant (see the documentation)
  • Test your command first by using -print instead of -delete

    find . ! -name u ! -name p -maxdepth 1 -type f -print
    
6
  • 7
    order of predicates is critical here. If one put -delete just after . it will be disaster (will delete all files in CWD) Jan 8, 2013 at 17:34
  • This could be written more compactly as find . -maxdepth 1 -type f -name '[^up]' -delete
    – kojiro
    Jan 8, 2013 at 18:09
  • 4
    @kojiro yes but only for files that are just one letter. With more complex names the regex could be a mess.
    – Matteo
    Jan 8, 2013 at 20:15
  • 3
    find is my best friend, especially when there are too many files to glob Jan 9, 2013 at 22:36
  • +1 for the explanation of all arguments Apr 28, 2020 at 19:20
44

Simple:

mv the files you want in a upper directory, rm the directory and then mv them back.

5
  • 13
    Offcourse, mv them to a directory higher. Try not to mv them to a subdirectory you are deleting...
    – Konerak
    Jan 8, 2013 at 19:21
  • @Konerak: rm without -r won't remove subdirectories. Jan 8, 2013 at 23:07
  • 11
    This will overwrite files with the same name in the destination directory
    – Matteo
    Jan 9, 2013 at 9:29
  • 10
    I am downvoting this because while it can be handy, it also is non-atomic and effectively removes all files from the directory during a short period of time; this would not be acceptable if, for instance, the files are being shared on the network. Jan 10, 2013 at 12:19
  • Also you'd need write access on the parent directory, which you likely not have on a shared web server. Apr 4, 2020 at 20:57
16

Somewhat similar to this answer but no special options are needed, as far as I know the following is "ancient" functionality supported by any (vaguely) /bin/sh resembling shell (e.g. bash, zsh, ksh, etc)

rm [^up]
4
  • 2
    This works for the 1-char filenames. For longer names, sparkie's answer is better. Jan 8, 2013 at 15:31
  • 3
    What would be wrong with rm [^up]*? I do similar things rather often.
    – user
    Jan 8, 2013 at 15:33
  • 3
    @MichaelKjörling - this would delete all files beginning with either u or p, not just those with the names u and p. I think the OP (@Ashot) meant the a-z and u,p,etc. symbolically and not literally. Jan 9, 2013 at 9:07
  • 4
    @HobbesofCalvin That would delete all files not beginning with u or p, not those beginning with them.
    – rjmunro
    Jan 9, 2013 at 10:48
15

Doing it without find:

ls | grep -v '(u|p)' | xargs rm

(Edit: "u" and "v", as in other places here, are being used as generic versions of entire regexes. Obviously you'll want to be careful to anchor your regexes to avoid matching too many things.)

You're definitely going to want a script if you're going to be doing much of this, as others have suggested.

6
  • 1
    grep will not handle extended regexpt by default: either use -E or egrep
    – Matteo
    Jan 9, 2013 at 9:28
  • 2
    this will exclude any file containing a u or a p
    – Matteo
    Jan 9, 2013 at 9:28
  • @Matteo No it won't. The grep isn't grepping the files, it's grepping the output of the ls command. You're thinking of something like grep -L (u|p)' * | xargs rm where -L means list filenames not containing a match.
    – rjmunro
    Jan 9, 2013 at 10:52
  • 4
    Oh, you mean any file who's name contains u or p, not any file containing a u or a p. That is correct. You can fix by using egrep -v '^(u|p)$'
    – rjmunro
    Jan 9, 2013 at 12:40
  • 1
    Here is remove everything except these matches! ls | grep -v 'vuze\|progs' | xargs rm -rf
    – Nick
    Sep 25, 2014 at 12:32
6

In zsh:

setopt extended_glob  # probably in your .zshrc

then

rm ^(u|p)

or

rm *~(u|p)

The second will work even if you have ^ in $histchars for history substitution, and of course you can put an arbitrary glob before the ~.

5

GLOBIGNORE takes a colon-separated list

GLOBIGNORE=u:p
rm *
1
  • 12
    This does not work on my shell (GNU bash 4.1.5(1)). Be sure to test it first with something a little less harmful than rm or in a testing directory!
    – user
    Jan 8, 2013 at 20:09
3

Back in the floppy era I had a dos executable called "Except" that would move things out of the current directory temporarially and execute a command, so you could say:

except *.txt del *.*

to delete everything but your text files.

This would be a pretty trivial thing to implement as a shell script and if this is the kind of thing you are likely to do more than twice it seems like it would be a good idea.

1
  • 2
    It reminded me the same thing. But temporarily moving out of folder may not be a good idea in the era of multitasking :) Jan 9, 2013 at 9:18
3
 find . -maxdepth 1 ! -name "u" ! -name "p" -type f -exec rm -rf {} \;

This will delete all files except u and p in unix

2

For those preferring to specify arbitrary complex exclude patterns (spanning all affected filenames) in a full blown regexp emacs, posix-awk or posix-extended style (see find man page) I would recommend this one. It excludes u and p in current dir in this example. This may be handy for scripts.

find -regextype posix-awk ! -regex './(u|p)' -print0 | xargs -0 rm -rf
5
  • You need to specify a directory before the expression (`find . -regextype ...).
    – Matteo
    Jan 9, 2013 at 9:25
  • -regextype will only work on GNU versions
    – Matteo
    Jan 9, 2013 at 9:26
  • no - my find version (debian squeeze) does definitively not require an explicit directory before the expression if the current directory should be used
    – sparkie
    Jan 9, 2013 at 9:44
  • @sparke: this just works on GNU implementations
    – Matteo
    Jan 9, 2013 at 9:48
  • @sparkie: not defining the directory (first parameter) for find is a GNU extension of find command. The same applies for -regextype option. In addition, your command will delete files in subdirectories, too, whereas the original question clearly asked about files in a directory. Sep 4, 2015 at 5:28
2

Yet another:

for FILE in ./*; do if [[ $FILE != ./u* ]] || [[ $FILE != ./p* ]];then rm $FILE; fi; done;

It's kind of lengthy and I don't know if you could easily make it into an function that could easily accommodate and arbitrary number of arguments, but it works well.

And it's pure bash goodness.

1

I always use:

rm [a-o,q-t,v-z]*

This will allow you to define how granular you want to make it. So if you want to delete a through o and Z files you can use:

rm [a-o,z]*
1

Yet another version using xargs:

ls -1 | grep -v do_not_delete | xargs -I files rm "files"

Note that xargs -I is needed to handle filenames including spaces correctly.

1

Here's another variant. You can type:

rm -i *

or:

rm --interactive *

So rm will ask you to confirm deleting of each file.

1

Use:

find . -type f ! -name 'u' ! -name 'p' ! -name '*.ext' -delete
find . -type d ! -name 'u' ! -name 'p' ! -name '*.ext' -delete

in order to delete all files including directories, except u, p and .ext files.

0

A simple way that is hard to mess up: let's say you want to delete everything except *.pdf:

mkdir tmp
mv *.pdf tmp
rm *
mv tmp/* .
rm -r tmp

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.