138

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?

Firegun
  • 1,839
  • 3
  • 15
  • 9

9 Answers9

205

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
Martin Tournoij
  • 1,715
  • 2
  • 15
  • 35
Kevin
  • 40,767
  • Some of these may need to be adapted depending on whether you have folders and what you want to do with them. – Kevin Jun 05 '13 at 15:58
  • 3
    Shouldn't *.!(txt) be !(*.txt)? – Lri Jun 06 '13 at 08: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 06 '13 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. – CMCDragonkai Jan 17 '14 at 08:56
  • $ find . -type f ! -name "*.txt" | xargs rm – Ntwobike Oct 03 '17 at 15:10
  • @Ntwobike xargs rm will give error rm: missing operand when all files in the folder have *.txt extension (i.e. non-txt files were not found). This makes it impossible to use your variant in .sh-files when there are other commands after the failing one. – izogfif Jun 07 '18 at 04:45
  • 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 08 '18 at 07:52
  • This command does traverse directories further down, right? – ECII Nov 27 '22 at 20:56
  • @ECII the find command crawls the whole subtree, the globs do not. – Kevin Nov 28 '22 at 00:21
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)
Kent
  • 768
  • How can i use this to specify multiple extensions? – Jordan Mackie Jan 28 '20 at 16:38
  • shopt -s extglob is powerful. https://www.tecmint.com/delete-all-files-in-directory-except-one-few-file-extensions/ provides good examples. To delete all except certain extensions, rm -v !(*.zip|*.odt) works. – Martin_W Nov 23 '20 at 06:52
10

You can use inverted grep and xargs

ls | grep -v .txt$| xargs rm
rjv
  • 211
  • 1
    ls | grep -v *.txt | xargs rm works just as well – phillipsk Feb 27 '16 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 '16 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 '18 at 03:31
8

One solution without find:

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

This should work on all kind of shells.

michas
  • 21,510
  • 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 '22 at 07:01
5

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

Surya
  • 479
1

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

rm -rf $(ls -aI ".mydir")
eQ19
  • 111
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.

mcp
  • 717
-1
ls |awk '!/\.txt$/{print "rm -rvf "$1}'| sh