137

I know how to delete all txt file under current directory by rm *.txt. Does anyone know how to delete all files in current directory EXCEPT txt file?

3
  • 35
    As always, test the given answers with a harmless command like ls before actually attempting to call rm.
    – chepner
    Jun 5, 2013 at 15:58
  • 8
    ^ This guy doesn't like to live dangerously.. :) Oct 25, 2019 at 18:19
  • ^ Guess who just did :facepalm: lol
    – Drazisil
    Sep 11, 2022 at 1:20

9 Answers 9

203

You can use find:

find . -type f ! -name '*.txt' -delete

Or bash's extended globbing features:

shopt -s extglob
rm *.!(txt)

Or in zsh:

setopt extendedglob
rm *~*.txt(.)
#  ||     ^^^ Only plain files
#  ||^^^^^ files ending in ".txt"
#  | \Except
#   \Everything
9
  • Some of these may need to be adapted depending on whether you have folders and what you want to do with them.
    – Kevin
    Jun 5, 2013 at 15:58
  • 3
    Shouldn't *.!(txt) be !(*.txt)?
    – Lri
    Jun 6, 2013 at 8:32
  • 1
    @LauriRanta depends on what's in the folder, which we haven't gotten an answer to. It's fine as is if all the files have extensions, and rm would choke if there were folders.
    – Kevin
    Jun 6, 2013 at 13:22
  • I've got an issue with the brackets. When I use the globbing style in a bash script, it complains about a syntax error and the parentheses. However doing it from the CLI works. Jan 17, 2014 at 8:56
  • 1
    @izogfif check this find . -type f ! -name "*.txt" | xargs -r rm would work in GNU\xargs. BSD and UNIX xargs command may not have -r you have to check your local man xargs
    – Ntwobike
    Jun 8, 2018 at 7:52
24

If you just want to delete all files except '*.txt' then you can use the following command:

$ find . -type f ! -name "*.txt" -exec rm -rf {} \;

but if you also want to delete directories along with the files then you can use this:

$ find . ! -name "*.txt" -exec rm -r {} \;

17

there are many ways could do it. but the most simple way would be (bash):

shopt -s extglob
rm !(*.txt)
2
  • How can i use this to specify multiple extensions? Jan 28, 2020 at 16:38
  • shopt -s extglob is powerful. tecmint.com/… provides good examples. To delete all except certain extensions, rm -v !(*.zip|*.odt) works.
    – Martin_W
    Nov 23, 2020 at 6:52
10

You can use inverted grep and xargs

ls | grep -v .txt$| xargs rm
3
  • 1
    ls | grep -v *.txt | xargs rm works just as well
    – phillipsk
    Feb 27, 2016 at 13:32
  • @phillipsk grep -v *.txt will work only if there's exactly one .txt file. If there is none, grep will use *.txt as the pattern; if there's more than one, it will search for the first filename inside all of the other .txt files, ignoring the output from ls. (Exact results may depend on the shell's glob options.)
    – JigglyNaga
    Jul 21, 2016 at 16:11
  • 1
    .txt$ will match strings ending with txt regardless of the dot. Because grep takes regular expression as parameter. So files a.txt and aatxt and a-txt will all be matched by this expression. Correct expression should be ls | grep -v \\.txt$ | xargs --no-run-if-empty rm. For curious people: If you want to play around with the expression safely use this test expression ls | grep \\.txt$ | xargs --no-run-if-empty echo (note: there's no -v flag and rm=>echo). Note2: you may have noticed double backslash. One is for regex, another is for bash to escape slash.
    – Dimitry K
    Nov 19, 2018 at 3:31
8

One solution without find:

mv dir/*.txt otherdir/
rm -r dir
mv otherdir dir

This should work on all kind of shells.

1
  • I think this definitely has its place as it's the answer that an occasional user is most likely to remember in the long term.
    – Mehmet
    Mar 11, 2022 at 7:01
4

Simply do:

rm $(ls -I "*.txt" ) #Deletes file type except *.txt

Likewise, if need to delete "except one or more file type", do:

rm $(ls -I "*.txt" -I "*.pdf" ) #Deletes file types except *.txt & *.pdf

4
  • 1
    Welcome to StackExchange! The -I option could be useful, but see why not parse ls. And what does the | xargs achieve? Without any arguments, it just runs echo on all of its input.
    – JigglyNaga
    Jul 21, 2016 at 15:59
  • What is xargs supposed to achieve?
    – dhag
    Jul 21, 2016 at 16:00
  • Thanks, xargs was not useful in the above case, have corrected now.
    – Surya
    Jul 22, 2016 at 22:05
  • This won't work if there are spaces in the file names. xargs would have been better. So ls -1I '*.txt' | xargs -d'\n' rm
    – CR.
    Mar 9, 2022 at 1:23
1

This works also to remove all hidden (dot) files and folders except the stated one (.mydir):

rm -rf $(ls -aI ".mydir")
0

I made a modular bash function for this, based on a compilation of findings:

rmexcept()
{
    files=()
    for pattern in "$@"
    do
        files+=(`find . -maxdepth 1 -type f -not -iname "$pattern"`)
    done

    # filter for duplicates only when more than one pattern provided
    if (($# > 1))
    then
        printf "%s\n" ${files[@]} | sort | uniq -d | xargs rm
    else
        printf "%s\n" ${files[@]} | xargs rm
    fi
}

It is designed to work with multiple pattern arguments:

rmexcept '*.tex' '*.pdf'

NOTE: The single quotes are necessary! Otherwise bash will expand the wildcard and you will have a number of inputs equal to the matching expansion, which causes every file to eventually repeat, and thus, causes the function to delete everything!

If you don't want to remember this dangerous caveat (I don't), define rmexcept as follows:

rmexcept()
{
    files=()
    for pattern in "$@"
    do
        files+=(`find . -maxdepth 1 -type f -not -iname "*$pattern"`)
    done

    # filter for duplicates only when more than one pattern provided
    if (($# > 1))
    then
        printf "%s\n" ${files[@]} | sort | uniq -d | xargs rm
    else
        printf "%s\n" ${files[@]} | xargs rm
    fi
}

And use without wildcards:

rmexcept .tex .pdf

NOTE: You can still make a dangerous mistake by using a prefix *. I'll keep thinking about how to improve this.


The way I put the find results in an array might not be best practice. See this thread for more details.

-1
ls |awk '!/\.txt$/{print "rm -rvf "$1}'| sh
1
  • Why not parse ls (and what to do instead)? You’ve also got a wonderful command injection there — I hope you don’t care about any of the data on any systems on which you run similar commands, or that you don’t mind your personal data (SSH keys, GPG keys for example) being shared. Mar 9, 2022 at 8:31

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .