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?
9 Answers
You can use find:
find . -type f ! -name '*.txt' -delete
Or bash's extended globbing features:
shopt -s extglob
rm !(*.txt)
The entire *.txt glob should be negated to avoid matching on parts of file names (for example my.file.txt matches , but not *.!(txt)!(*.txt)).
Or in zsh:
setopt extendedglob
rm *~*.txt(.)
# || ^^^ Only plain files
# ||^^^^^ files ending in ".txt"
# | \Except
# \Everything
-
5
-
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
rmwould choke if there were folders.Kevin– Kevin2013-06-06 13:22:18 +00:00Commented Jun 6, 2013 at 13:22 -
1@izogfif check this
find . -type f ! -name "*.txt" | xargs -r rmwould work in GNU\xargs. BSD and UNIX xargs command may not have -r you have to check your localman xargsNtwobike– Ntwobike2018-06-08 07:52:57 +00:00Commented Jun 8, 2018 at 7:52 -
1This command does traverse directories further down, right?ECII– ECII2022-11-27 20:56:43 +00:00Commented Nov 27, 2022 at 20:56
-
1@ECII the
findcommand crawls the whole subtree, the globs do not.Kevin– Kevin2022-11-28 00:21:10 +00:00Commented Nov 28, 2022 at 0:21
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 {} \;
there are many ways could do it. but the most simple way would be (bash):
shopt -s extglob
rm !(*.txt)
-
@StephenKitt is referring to an incorrect comment I made, deleted to avoid confusion: "Will also delete myfile.something.txt (I speak from bitter experience)."Andrew Swift– Andrew Swift2024-09-19 10:46:54 +00:00Commented Sep 19, 2024 at 10:46
-
@AndySwift thanks for clarifying, that helps with the review of your suggested edit!Stephen Kitt– Stephen Kitt2024-09-19 11:07:28 +00:00Commented Sep 19, 2024 at 11:07
You can use inverted grep and xargs
ls | grep -v .txt$| xargs rm
-
1
.txt$will match strings ending withtxtregardless of the dot. Becausegreptakes regular expression as parameter. So filesa.txtandaatxtanda-txtwill all be matched by this expression. Correct expression should bels | 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 expressionls | grep \\.txt$ | xargs --no-run-if-empty echo(note: there's no-vflag andrm=>echo). Note2: you may have noticed double backslash. One is for regex, another is for bash to escape slash.Dimitry K– Dimitry K2018-11-19 03:31:13 +00:00Commented Nov 19, 2018 at 3:31
One solution without find:
mv dir/*.txt otherdir/
rm -r dir
mv otherdir dir
This should work on all kind of shells.
-
1I 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– Mehmet2022-03-11 07:01:19 +00:00Commented Mar 11, 2022 at 7:01
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
This works also to remove all hidden (dot) files and folders except the stated one (.mydir):
rm -rf $(ls -aI ".mydir")
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.
ls |awk '!/\.txt$/{print "rm -rvf "$1}'| sh
-
2Why 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.Stephen Kitt– Stephen Kitt2022-03-09 08:31:41 +00:00Commented Mar 9, 2022 at 8:31
lsbefore actually attempting to callrm.