232

I have a directory containing a large number of files. I want to delete all files except for file.txt . How do I do this?

There are too many files to remove the unwanted ones individually and their names are too diverse to use * to remove them all except this one file.

Someone suggested using

rm !(file.txt)

But it doesn't work. It returns:

Badly placed ()'s 

My OS is Scientific Linux 6.

Any ideas?

  • 13
    move the one you want to keep, then rm the others? – Olivier Dulac Sep 5 '14 at 9:04
  • 5
    @OlivierDulac Are we the only two people in this question who aren't overthinking the question? – Shadur Sep 5 '14 at 12:33
  • 1
    @shadur: well, I can relate to them: oneliners are attractive... ^^ – Olivier Dulac Sep 5 '14 at 12:57
  • 1
    Thanks, and yes I was looking for a one-line solution. It's too time consuming to keep moving files around as I have to do this quite often. – Kantura Sep 9 '14 at 4:37
278

POSIXly:

find . ! -name 'file.txt' -type f -exec rm -f {} +

will remove all regular files (recursively, including hidden ones) except file.txt. To remove directories, change -type f to -type d and add -r option to rm.

In bash, to use rm -- !(file.txt), you must enable extglob:

$ shopt -s extglob 
$ rm -- !(file.txt)

(or calling bash -O extglob)

Note that extglob only works in bash and Korn shell family. And using rm -- !(file.txt) can cause an Argument list too long error.

In zsh, you can use ^ to negate pattern with extendedglob enabled:

$ setopt extendedglob
$ rm -- ^file.txt

or using the same syntax with ksh and bash with options ksh_glob and no_bare_glob_qual enabled.

  • 8
    Specifying the directory is good practice (fullpath in this case? or maybe add a warning here that this command deletes every file starting from the current working directory ?). I also usually write any example with rm as echo rm instead, and ask people to only take out the echo when they are really sure it will do what they want. Other than that, +1 for the thorough answer – Olivier Dulac Sep 5 '14 at 9:00
  • 11
    I'd suggest -delete instead of -exec, shorter and easier to remember – Izkata Sep 5 '14 at 15:41
  • 6
    @Izkata: -delete is not defined by POSIX. – cuonglm Sep 5 '14 at 15:42
  • 4
    How to exclude list of files? – Meysam Sep 6 '14 at 4:48
  • 4
    @Meysam - see my answer for a solution that will handle a list of files. Else with Gnouc's find solution you can do ! \( -name one_file -o -name two_file \) and so on. – mikeserv Sep 6 '14 at 15:35
103

Another take in a different direction (iff there are no spaces in file names)

ls | grep -v file.txt | xargs rm

or (works even if there are spaces in file names)

ls | grep -v file.txt | parallel rm

from man grep:

 -v, --invert-match
          Invert the sense of matching, to select non-matching lines.  (-v is specified by POSIX)
  • 2
    It will not work if file names have a space ... – Matteo Feb 8 '16 at 16:07
  • 1
    Ciao @Matteo, it works for files with spaces too, but you need to surround the grep-pattern by quotes, e.g. ls | grep -v "a file with spaces.bin" | xargs rm. This is normal grep syntax. – Sebastian Feb 8 '16 at 19:14
  • 1
    @Sebastian The problem is not the grep but rm. rm will get a list of space separated arguments. Try touch 'a b'; touch 'c d'; ls | grep -v 'a b' | xargs rm: you will get rm: c: No such file or directory and rm: d: No such file or directory – Matteo Feb 9 '16 at 7:57
  • 1
    Yes, this is a common problem. See the options -print0 in find and -0 in xargs. Here is a workaround, but is is slightly inconvenient. find . -maxdepth 1 -type f | grep -v 'a b' | tr '\n' '\0' | xargs -0 rm. By the way, gnu parallel handles this well (I almost always use it as a substitute for xargs: ls | grep -v 'a b' | parallel rm – Sebastian Feb 11 '16 at 7:10
  • 1
    It's not only spaces, it's all blanks and newlines, but also quoting characters (single, double quotes and backslash) and filenames starting with -. – Stéphane Chazelas Aug 30 '16 at 9:52
28
+50

Maintain a copy, delete everything, restore copy:

{   rm -rf *
    tar -x
} <<TAR
$(tar -c $one_file)
TAR

In one line:

{ rm -rf *; tar -x; } <<< $(tar -c $one_file)

But that requires a shell that supports here-strings.

  • 9
    This is somewhat mind-blowing. – vschum Sep 5 '14 at 2:36
  • 2
    @Derek - please see the edit. – mikeserv Sep 5 '14 at 2:49
  • 2
    But if this gets interrupted halfway through, or if anything else goes wrong, the file is gone. – kasperd Sep 5 '14 at 8:04
  • 2
    @kasperd - no. Only if the parent shell dies between the time it runs rm and tar -x. rm can fail as much as it wants. – mikeserv Sep 5 '14 at 8:05
  • 2
    Isn't more efficient to move it to another directory and move it back? We don't need to deal with the content of the file, only with its path. – leonbloy Sep 6 '14 at 15:51
19

you're all overthinking this.

cd ..
mv fulldir/file.txt /tmp/
rm -rf fulldir
mkdir fulldir
mv /tmp/file.txt fulldir/

Done.

EDIT Actually, easier:

cd ..
ln fulldir/file.txt ./
rm -rf fulldir
mkdir -p fulldir
mv file.txt fulldir/
  • 3
    that's the same thing my answer does. exce[pt it doesn't lose any permissions on the dir. – mikeserv Sep 5 '14 at 12:50
  • 5
    If it's a large file on a separate filesystem from /tmp and you're trying to remove everything from the root of the filesystem down, then moving it somewhere safe may not be an option. – Johnny Sep 6 '14 at 5:24
  • 1
    This is definitely the simplest answer. – Ben Liyanage Oct 18 '16 at 23:10
16

On my Scientific Linux 6 OS this works:

shopt -s extglob
rm !(file.txt)

I also have Debian 32bit installed on a Virtual Machine. The above does not work but the following does:

find . -type f ! -name 'file.txt' -delete
  • 1
    You mean the find solution I gave didn't working? – cuonglm Sep 5 '14 at 2:30
  • "didn't work" or " isn't working". Yes your find solution also works. Thanks. – Kantura Sep 5 '14 at 2:43
  • 1
    Can you make it more details? How "didn't work? It does not remove other files, or it removed file.txt or anything else? – cuonglm Sep 5 '14 at 2:45
  • I meant that on the Debian OS "$rm !(file.txt)" returns "Badly placed ()'s". So I tried "$shopt -s extglob", but it returned: "shopt: Command not found". Then I tried the "find" solution. It does work. – Kantura Sep 5 '14 at 2:55
  • 1
    Oh, so of course it doesn't work. shopt is not a tcsh builtin. – cuonglm Sep 5 '14 at 3:15
8

Use rm !("file.txt") instead of rm !(file.txt)

  • 4
    That makes absolutely no difference whatsoever. The issue here was that the OP was 1) not using a shell that supports this format and 2) even in bash, you need to enable it with shopt -s extglob. In any case, quoting a simple filename like that would have made no difference. – terdon Sep 6 '14 at 11:15
  • 1
    To keep just utils folder do - rm -rf !("utils") – James Jithin Feb 1 '18 at 18:27
6

Just to give a different answer, you can use the default behavior of rm that it won't delete folders:

mkdir tmp && mv file.txt tmp  # create tmp dir and move files there
rm                            # delete all other files
mv tmp/* . && rm -rf tmp      # move all files back and delete tmp dir

protected by Community Jan 12 '16 at 22:32

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.