52

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.

Lesmana
  • 18,754
Starkers
  • 3,149

13 Answers13

67
cd <the directory you want>
find . -type f ! -iname "*.pdf" -delete
  • The first command will take you to the directory in which you want to delete your files
  • The second command will delete all files except with those ending with .pdf in filename

For 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.

NOTE: this command will delete all files (except pdf files but including hidden files) in current directory as well as in all sub-directories. ! 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
Alex Jones
  • 8,192
45

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

steeldriver
  • 142,475
19

Delete to trash:

$ 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
αғsнιη
  • 36,350
14

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!

Jerry
  • 141
4

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
Cyrus
  • 5,789
3

Be careful and compose: use xargs

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 everything
  • ls | 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 deletion

As I said, I mainly like this for the safety it provides: no accidental rm * for me. Two other advantages:

  • It's composable; you can use 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.
  • You can use each tool for its main purpose. I prefer to use 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.

Update

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.

3

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.

mic_e
  • 131
2

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.

Pablo Bianchi
  • 17,371
Alex Jones
  • 8,192
1
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}')
muru
  • 207,228
1
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
shalomb
  • 251
1

portable shell way

$ 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:

$ 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'

alternative python

python -c 'import os;map(lambda x: os.remove(x), filter(lambda x: not x.endswith(".pdf"),os.listdir(".")))'
muru
  • 207,228
0

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*)
KeitelDOG
  • 101
0

This solution would work for most cases.

I got hundreds of directories but I have exception for some directories files.

  1. 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
    
  2. 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)
    
  3. Unzip my_files.zip

    unzip my_files.zip
    
xyonme
  • 101