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.
Ask Ubuntu is a question and answer site for Ubuntu users and developers. It only takes a minute to sign up.
Sign up to join this communitycd <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 .PDF
To delete only in current directory and not in sub-directories add -maxdepth 1
:
find . -maxdepth 1 -type f ! -iname "*.pdf" -delete
.
means "and"? !
means "except" -name
signifies that you want to exclude by a name parameter and then -delete
is the action to take upon finding? So it looks for everything except "*.pdf" and deletes them? Or have I misunderstood?
Dec 1, 2014 at 12:50
.
means current directory. !
means to take all files except the one with .pdf
at the end. -delete
means to delete it. am i clear now ?
Dec 1, 2014 at 12:57
-maxdepth 1
parameter to begin with. Then suggest removing the parameter in case one wants to delete recursively.
Dec 2, 2014 at 12:59
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
rm *~*.pdf
Dec 1, 2014 at 15:18
!(*.py)
. Also, presumably, if the OP wants only ".pdf" files remaining, then files without extensions should also be deleted and not ignored.
Dec 1, 2014 at 20:50
$ 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.
pdf
anywhere in its name. --- b) It will delete PDF files if any of the letter in the suffix are upper case. --- c) It is not a good idea to use output of ls
. It will not work with file names containing newlines. Some implementations of ls
replace special characters e.g. tab by ?
. --- It is better to use: find -maxdepth 1 -print0
. (not so short as ls
:) ----- To resolve a) and b) use grep -vi '\.pdf$'
--- complete (but GNU only) solution: find -maxdepth 1 -print0 | grep -viz '\.pdf$' | xargs -0 rm
Dec 1, 2014 at 22:12
| head -20
to at least see if it looks roughly correct, whereas if you just rm my_pattern
, you have no chance to spot a big mistake.
Dec 2, 2014 at 14:13
find . -type f ! -name "*.pdf"
to print to console, or pipe to less or a file. [and then pipe to xargs to rm if desired like pabouk's comments (with the -print0| ... -0 for weird filenames)]
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.
for item in [f for f in os.listdir('.') if not f.endswith('.pdf')]: os.remove(item)
Dec 4, 2014 at 16:59
python -c "import os; for f in os.listdir('.'): if not f.endswith('.pdf'): os.remove(f)"
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