I have a directory that contains the following:
x.pdf
y.zip
z.mp3
a.pdf
I want to delete all files apart from x.pdf and a.pdf. How do I do this from the terminal? There are no subdirectories so no need for any recursion.
I have a directory that contains the following:
x.pdf
y.zip
z.mp3
a.pdf
I want to delete all files apart from x.pdf and a.pdf. How do I do this from the terminal? There are no subdirectories so no need for any recursion.
cd <the directory you want>
find . -type f ! -iname "*.pdf" -delete
.pdf in filenameFor example, if there is a directory called temp in your home folder:
cd ~/temp
then delete files:
find . -type f ! -iname "*.pdf" -delete
This will delete all files except xyz.pdf.
You can combine these two commands to:
find ~/temp -type f ! -iname "*.pdf" -delete
. is the current directory. ! means to take all files except the ones with .pdf at the end. -type f selects only files, not directories. -delete means to delete it.
! must come before -name. simply -name will include only .pdf, while -iname will include both .pdf and .PDFTo delete only in current directory and not in sub-directories add -maxdepth 1:
find . -maxdepth 1 -type f ! -iname "*.pdf" -delete
With bash's extended shell globbing, you could remove any files with extensions other than .pdf using
rm -- *.!(pdf)
As noted by @pts, the -- characters indicate the end of any command options, make the command safe in the rare case of files whose names start with a - character.
If you want to delete files without any extension as well as those with extensions other than .pdf, then as pointed out by @DennisWilliamson you could use
rm -- !(*.pdf)
Extended globbing should be enabled by default, but if not you can do so using
shopt -s extglob
Especially if you intend to use this inside a script, it's important to note that if the expression doesn't match anything (i.e. if there are no non-pdf files in the directory), then by default the glob will be passed unexpanded to the rm command, resulting in an error like
rm: cannot remove `*.!(pdf)': No such file or directory
You can modify this default behaviour using the nullglob shell option, however that has its own issue. For a more thorough discussion see NullGlob - Greg's Wiki
$ cd <the directory you want>
$ gvfs-trash !(*.pdf)
Or via mv command (but in this way you cannot restore it from Trash since it doesn't record .trashinfo information, so this means you moved your files to a destination where it is as following).
mv !(*.pdf) ~/.local/share/Trash/files
The easiest approach: Create another directory somewhere (if you're only deleting in one directory, not recursively, it can even be a subdirectory); move all the .pdf's there; delete everything else; move the pdf's back; delete the intermediate directory.
Quick, easy, you can see exactly what you're doing. Just make sure the intermediate directory is on the same device as the directory you're cleaning up so that moves are renames, not copies!
Use bash's GLOBIGNORE:
GLOBIGNORE=x.pdf:a.pdf
rm *
unset GLOBIGNORE
From bash's man page:
GLOBIGNORE:
A colon-separated list of patterns defining the set
of filenames to be ignored by pathname expansion.
A quick test:
mkdir /tmp/foooooo
cd /tmp/foooooo
touch x.pdf y.zip z.mp3 a.pdf
GLOBIGNORE=x.pdf:a.pdf
ls -1 *
Output:
y.zip z.mp3
Here's an approach I like, because it lets me be very careful: compose a way to show just the files I want to delete, then send them to rm using xargs. For example:
ls shows me everythingls | grep pdf shows me the files I want to keep. Hmm.ls | grep -v pdf shows the opposite: all except what I want to keep. In other words, it shows the list of things I want to delete. I can confirm this before doing anything dangerous.ls | grep -v pdf | xargs rm sends exactly that list to rm for deletionAs I said, I mainly like this for the safety it provides: no accidental rm * for me. Two other advantages:
ls or find to get the initial list, as you prefer. You can use anything else you like in the process of narrowing that list - another grep, some awk, or whatever. If you needed to delete only files whose names contain a color, you could build it up this same way.find for finding and rm for removal, as opposed to having to remember that find accepts a -delete flag. And if you do this, again, you can compose alternate solutions; maybe instead of rm, you could create a trash command that moves the file to the trash (allowing "undeletion") and pipe to that instead of rm. You don't need to have find support that option, you just pipe to it.See comments by @pabouk for how modify this to handle some edge cases, such as line breaks in file names, filenames like my_pdfs.zip, etc.
I usually solve such problems from the interactive Python interpreter:
mic@mic ~ $ python
>>> import os
>>> for f in os.listdir('.'):
... if not f.endswith('.pdf'):
... os.remove(f)
It might be longer than a one-liner with find or xargs, but it's extremely resilient, and I know exactly what it does, without having to research it first.
Better answer (compared to my previous answer) to this question would be by using the powerful file command.
$ file -i abc.pdf
abc: application/pdf; charset=binary
now your problem:
cd <the directory you want to search in>
for var in ./*
do
if file -i "$var" | grep -q 'application/pdf\;'
then
echo "$var"
fi
done
The job of for command is give the files in current directory in the form of variable $var. if-then command outputs the names of pdf files by taking the exit status of 0 from file -i "$var" | grep -q 'application/pdf\;' command, it will give exit status of 0 only if it finds pdf files.
rm $(ls -lo|grep -v [Pp][Dd][Ff]$|awk '{print $7}')
Warning! Better try first
ls -l $(ls -lo|grep -v [Pp][Dd][Ff]$|awk '{print $7}')
rm -i -- !(*@(a|x).pdf)
Read as, remove all files that are not a.pdf or x.pdf.
This works by making use of 2 extended globs, the outer !() to negate the contained glob which itself requires that the glob must match one or more of a or x patterns before the .pdf suffix. See glob#extglob.
$ ls -a
.dotfile1 .dotfile2 a.pdf x.pdf y.zip z.mp3
$ echo -- !(a.pdf)
-- x.pdf y.zip z.mp3
$ echo -- !(x.pdf)
-- a.pdf y.zip z.mp3
$ echo -- !(a.pdf|x.pdf)
-- y.zip z.mp3
$ echo -- !(@(a|x).pdf) # NOTE.that this matches the .dotfiles* as well
-- . .. .dotfile1 .dotfile2 y.zip z.mp3
$ echo -- !(*@(a|x).pdf) # but this doesn't
-- y.zip z.mp3
$ echo rm -i -- !(*@(a|x).pdf)
rm -i -- y.zip z.mp3
$ ksh -c 'for i in ./*; do case $i in *.pdf)continue;; *)rm "$i";; esac;done'
Pretty much POSIX and compatible with any Bourne-style shell ( ksh, bash, dash ). Well suited for portable scripts and when you can't use bash's extended shell globbing.
$ perl -le 'opendir(my $d,"."); foreach my $f (grep(-f && !/.pdf/ , readdir($d))){unlink $f};closedir $d'
Or slightly cleaner:
$ perl -le 'opendir(my $d,"."); map{ unlink $_ } grep(-f "./$_" && !/.pdf/ , readdir($d));closedir $d'
python -c 'import os;map(lambda x: os.remove(x), filter(lambda x: not x.endswith(".pdf"),os.listdir(".")))'
Be Careful of what you're deleting!
A safe way to test it before trying to delete is to test first with ls, as some uncaught behaviors could delete unwanted files. And you can do it directly outside of the directory. ls is similar to rm, so :
ls sub/path/to/files/!(*.pdf)
This will list
y.zip
z.mp3
And now you can see what you're deleting and can safely delete them :
rm sub/path/to/files/!(*.pdf)
And that's it. Yo can use wildcard * to be more selective like keeping only programming course documents :
rm sub/path/to/files/!(*programming*)
This solution would work for most cases.
I got hundreds of directories but I have exception for some directories files.
Zip all files that is not for deletion to my_files.zip. If there are directories, you need to add the recursive command.
zip -r my_files.zip a.php b.txt c.dll directory1 directory2
Delete all files recursively(thoroughly) except myfiles.zip
#extended globbing allows for more advanced pattern matching.
shopt -s extglob
#delete all file
rm -r -- !(my_files.zip)
Unzip my_files.zip
unzip my_files.zip