How to delete all files in a directory except some?

123

58

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?

Ashot

Posted 2013-01-08T12:17:05.293

Reputation: 1 349

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 – 2013-01-15T05:13:05.780

1If you also want to delete hidden files run shopt -s dotglob before running rm (...). – None – 2013-01-28T08:07:09.170

Answers

108

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.

Der Hochstapler

Posted 2013-01-08T12:17:05.293

Reputation: 77 228

Works in practice but can't be scripted. I think the find solution is the best. – flungo – 2015-05-10T14:10:12.007

@Oliver Salzburg, the combination is not working on my Korn shell , what I might be doing wrong could you please advise? solution from slowpoision , esc followed by * worked though. Thanks – Forever Learner – 2018-02-02T11:22:02.510

25I guess this works only as long as the list of files which * expands too isn't getting too long. :-} – Frerich Raabe – 2013-01-08T16:57:26.083

1@FrerichRaabe: Indeed. If it's too long a different approach will be required. Luckily we now have a list of great options :) – Der Hochstapler – 2013-01-08T17:00:13.020

9Esc followed by * will also expand the "*". – slowpoison – 2013-01-09T20:45:32.770

This only works with rm; was looking for mv, cp, chmod etc.. :( – Santosh Kumar – 2013-01-17T15:46:30.697

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. – Der Hochstapler – 2013-01-17T18:18:26.970

2@OliverSalzburg Sorry, the combination is little bit confusing. I think you should write like Ctrl + Shift + x + * – Santosh Kumar – 2013-01-18T05:22:06.807

2@SantoshKumar: But that's not the combination. – Der Hochstapler – 2013-01-18T11:26:46.887

144

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

sparkie

Posted 2013-01-08T12:17:05.293

Reputation: 2 110

4If 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 – 2015-07-24T21:46:44.760

1you must have 'extglobbing' active: shopt -s extglob – sparkie – 2013-01-08T13:07:14.107

18You 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 – 2013-01-08T13:07:48.827

71

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
    

Matteo

Posted 2013-01-08T12:17:05.293

Reputation: 6 553

5order of predicates is critical here. If one put -delete just after . it will be disaster (will delete all files in CWD) – Michał Šrajer – 2013-01-08T17:34:36.027

This could be written more compactly as find . -maxdepth 1 -type f -name '[^up]' -delete – kojiro – 2013-01-08T18:09:11.590

4@kojiro yes but only for files that are just one letter. With more complex names the regex could be a mess. – Matteo – 2013-01-08T20:15:20.807

2find is my best friend, especially when there are too many files to glob – Terence Johnson – 2013-01-09T22:36:15.923

43

Simple:

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

user176581

Posted 2013-01-08T12:17:05.293

Reputation: 607

13Offcourse, mv them to a directory higher. Try not to mv them to a subdirectory you are deleting... – Konerak – 2013-01-08T19:21:34.453

@Konerak: rm without -r won't remove subdirectories. – reinierpost – 2013-01-08T23:07:23.937

11This will overwrite files with the same name in the destination directory – Matteo – 2013-01-09T09:29:22.597

8I 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. – sam hocevar – 2013-01-10T12:19:50.610

15

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]

hlovdal

Posted 2013-01-08T12:17:05.293

Reputation: 2 760

2This works for the 1-char filenames. For longer names, sparkie's answer is better. – glenn jackman – 2013-01-08T15:31:23.417

3What would be wrong with rm [^up]*? I do similar things rather often. – a CVn – 2013-01-08T15:33:09.920

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. – Sudipta Chatterjee – 2013-01-09T09:07:50.177

4@HobbesofCalvin That would delete all files not beginning with u or p, not those beginning with them. – rjmunro – 2013-01-09T10:48:48.693

@rjmunro - thanks, missed that one! :) – Sudipta Chatterjee – 2013-01-09T16:49:42.137

11

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.

tquid

Posted 2013-01-08T12:17:05.293

Reputation: 137

1Here is remove everything except these matches! ls | grep -v 'vuze\|progs' | xargs rm -rf – Nick – 2014-09-25T12:32:02.513

grep will not handle extended regexpt by default: either use -E or egrep – Matteo – 2013-01-09T09:28:23.720

2this will exclude any file containing a u or a p – Matteo – 2013-01-09T09:28:44.283

@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 – 2013-01-09T10:52:15.440

@rjmunro Yes it will: touch u uu; ls | egrep -v '(u|p)' gives an empty ouput – Matteo – 2013-01-09T12:28:54.477

4Oh, 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 – 2013-01-09T12:40:25.020

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 ~.

poolie

Posted 2013-01-08T12:17:05.293

Reputation: 165

5

GLOBIGNORE takes a colon-separated list

GLOBIGNORE=u:p
rm *

W_Whalley

Posted 2013-01-08T12:17:05.293

Reputation: 3 212

12This 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! – a CVn – 2013-01-08T20:09:18.630

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.

Bill K

Posted 2013-01-08T12:17:05.293

Reputation: 267

2It reminded me the same thing. But temporarily moving out of folder may not be a good idea in the era of multitasking :) – Sedat Kapanoglu – 2013-01-09T09:18:39.083

3

 find . -maxdepth 1 ! -name "u" ! -name "p" -type f -exec rm -rf {} \;

This will delete all files except u and p in unix

user1803687

Posted 2013-01-08T12:17:05.293

Reputation: 31

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

sparkie

Posted 2013-01-08T12:17:05.293

Reputation: 2 110

@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. – Mikko Rantalainen – 2015-09-04T05:28:05.767

You need to specify a directory before the expression (`find . -regextype ...). – Matteo – 2013-01-09T09:25:52.133

-regextype will only work on GNU versions – Matteo – 2013-01-09T09:26:13.023

no - my find version (debian squeeze) does definitively not require an explicit directory before the expression if the current directory should be used – sparkie – 2013-01-09T09:44:47.550

@sparke: this just works on GNU implementations – Matteo – 2013-01-09T09:48:25.597

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.

Renzo Fabián

Posted 2013-01-08T12:17:05.293

Reputation: 11

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]*

J Baron

Posted 2013-01-08T12:17:05.293

Reputation: 800

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.

sebhofer

Posted 2013-01-08T12:17:05.293

Reputation: 111

1

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.

dylnmc

Posted 2013-01-08T12:17:05.293

Reputation: 161

1

Here's another variant. You can type:

rm -i *

or:

rm --interactive *

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

idzikovsky

Posted 2013-01-08T12:17:05.293

Reputation: 11

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

talloaktrees

Posted 2013-01-08T12:17:05.293

Reputation: 111