Ask Ubuntu is a question and answer site for Ubuntu users and developers. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I need to remove all files with .gif extension except one file with name say "filename.gif". What is the optimal way to go about doing this in terminal?

The command rm *.gif removes all gif files including the file filename.gif.

share|improve this question
up vote 63 down vote accepted

Here's the simple solution you should probably use:

mv filename.gif filename.gif.keep
rm *.gif
mv filename.gif.keep filename.gif

There's nothing special about the .keep extension, this just makes it so that the filename temporarily doesn't end in .gif.

If you must not rename the file (and there are scripting situations where this is important):

for X in *.gif; do
    if [ "$X" != "filename.gif" ]; then
        rm "$X"

Or you can write it shorter like this:

for X in *.gif; do [ "$X" != "filename.gif" ] && rm "$X"; done

You may prefer to use find instead; it's very powerful, you might consider it more readable, and it better handles weird filenames with characters like * in them.

find . -maxdepth 1 -not -name 'filename.gif' -name '*.gif' -delete

I've used the -not operator for readability, but if POSIX compliance is important--if you're not using GNU find, or if this is for a script you intend to redistribute to others or run on a variety of systems--you should use the ! operator instead:

find . -maxdepth 1 ! -name 'filename.gif' -name '*.gif' -delete

One handy thing about find is that you can easily modify the command for case-insensitivity, so that it finds and deletes files with extensions like .GIF too:

find . -maxdepth 1 -not -name 'filename.gif' -iname '*.gif' -delete

Please note that I've used -iname in place of -name for the search pattern *.gif but I have not used it for filename.gif. Presumably you know exactly what your file is called, and -iname will match alternate capitalization not just in the extension, but anywhere in the filename.

All these solutions only delete files residing immediately in the current directory. They don't delete files not contained in the current directory, and they don't delete files that reside in subdirectories of the current directory.

If you want to delete files everywhere contained within the current directory (that is, including in subdirectories, and in subdirectories of those subdirectories, and so forth--files contained within the current directory or any of its descendants), use find without maxdepth -1:

find . -not -name 'filename.gif' -name '*.gif' -delete

Be careful with this!

You can also set other values with -maxdepth. For example, to delete files in the current directory and its children and grandchildren but not any deeper:

find . -maxdepth 3 -not -name 'filename.gif' -name '*.gif' -delete

Just make sure you never put -delete first, before the other expressions! You'll see I've always put -delete at the end. If you put it at the beginning, it would be evaluated first, and all the files under . (including files not ending in .gif and files in deep sub-sub-sub...directories of .) would be deleted!

For more information, see the manual pages for bash and sh and for the commands used in these examples: mv, rm, [, and (especially) find.

share|improve this answer
Thanks Eliah. I did it similar to the first method you have suggested i.e. to convert the file in a different format and convert it back. But that seemed a bit sub-optimal and unclean. I like the second approach you and @efthialex have suggested and I am using it now. Thanks! – user27323 Feb 15 '13 at 18:45
@Marvis I'm glad this met your needs. By the way, I've expanded this answer with information about another method (or class of methods) using find, which is highly versatile. – Eliah Kagan Feb 15 '13 at 18:59
Thanks for the detailed answer find is indeed a good find. – user27323 Feb 15 '13 at 19:09
+1 for the commonsense (move-delete-move) answer. – tudor Feb 18 '13 at 22:49
Nit-pick: how does find handle weird filenames with * in them better than the for-loop? The for loop handles files with * fine, because you've used double quotes. – Flimm Feb 23 '13 at 20:13
up vote 25 down vote

If you're using bash (the default shell), the extglob shell option allows you to use an extended pattern matching syntax. To enable it, use the shopt builtin command:

shopt -s extglob

(I include that line in my .bashrc file.)

Among other things, it grants access to the !() operator, which matches any pattern not inside the parens. For your purpose:

rm !(filename).gif

More information is available in man bash under "Pattern Matching".

share|improve this answer
Excellent answer! It's bash-specific so may not be suitable for portable scripting, but interactive tasks and even some scripting, this is hands-down the best way to go. It carries the added complexity that extglob must be enabled, but nothing bad happens if it's disabled (in that case, this just doesn't work). And it seems to be enabled by default, even though I don't have shopt -s extglob in any of my (or the global) configuration files. If there's a simple explanation for why it works for me out of the box, you may want to add it to this answer; or I may post a new question. – Eliah Kagan Feb 18 '13 at 22:34

Open a Terminal with Ctrl + Alt + T and type:

find . -type f -name "*.gif" -and -not -name "filename.gif" -exec rm -vf {} \;

The difference between -delete and -exec rm -vf {} \; is that with the second option, you will be able to see which files have been removed, because of the -v flag. That's something that can't be done with the -delete option. (-name -delete will print the filenames, but rm with -v reveals if each file was successfully deleted.)

share|improve this answer
Why is the -rf needed? @EliahKagan Any file or only files with .gif? – Seth Feb 15 '13 at 18:17
@Seth It will probably not delete all files, but if there is any .gif file with a space in it, this can cause non-.gif files to be deleted. For example, if the directory contains foo bar.gif and foo, foo will also be deleted. – Eliah Kagan Feb 15 '13 at 18:36
@EliahKagan I updated my post. – efthialex Feb 15 '13 at 18:47
@efthialex Thanks! – Eliah Kagan Feb 15 '13 at 18:52
+1. Thanks efthialex. – user27323 Feb 15 '13 at 19:06

If you can't use extglob as suggested by eswald, you could use command substitution instead:

rm $(ls *.gif | grep -v filename)

You can adapt this for other needs, such as removing all but the ten latest log files:

rm $(ls -rt *.log | head -n -10)

But beware, this doesn't take proper account of spaces; to make it more robust I'd rewrite it as a Python script.

Replace rm with echo to preview the matches.

share|improve this answer
This is a good example of what you should never do: parse the output of ls. – gniourf_gniourf Feb 16 '13 at 13:20
@gniourf_gniourf You're right that parsing ls should be avoided. But there's a much more serious problem: grep -v filename often matches files besides filename (or filename.gif), and sometimes doesn't match filename. For example, the regular expression oat-meal matches oat-meal.gif but also boat-mealtime.gif. matches and foo.bar2.gif but also foodbar.gif and footbar.gif. house[big] doesn't match house[big].gif or even house[big] itself. And of course if filename has whitespace it should be quoted. – Eliah Kagan Feb 16 '13 at 13:57
The most serious problems can be fixed by changing the command to rm $(ls *.gif | grep -Fxv 'filename.gif'). (Make sure to pass the filename with the .gif extension to grep, as this only matches whole lines.) A similar solution that avoids using ls for input is to use rm $(for X in *.gif; do echo "$X"; done | grep -Fxv 'filename.gif'). This works, but is sufficiently long and complicated that a grep-based solution is probably not really desirable. While ls should be avoided for input, it's great for previewing (so you can replace rm with ls, and this is nicer than echo). – Eliah Kagan Feb 16 '13 at 14:11
@EliahKagan - most of the problems you cited are solved with \ . Not saying the other isn't a better solution, but it's a grepping problem. – RobotHumans Feb 19 '13 at 1:09
@AbrahamVanHelpsing To use \ properly you have to understand what characters in the filename need to be escaped. So additional expertise is needed, plus this becomes a problem to be solved separately for every filename. Also, you're overstating the utility of \ in addressing the issues I've raised. Characters like [ and . can be escaped with \. That oat-meal matches boat-mealtime is independent of whether or not characters are escaped. So filename will still match other files. And parsing ls is still ugly at best. – Eliah Kagan Feb 19 '13 at 6:13

There's the GLOBIGNORE variable. From man bash:

      A colon-separated list of patterns defining the set of filenames
      to be ignored by pathname expansion.  If a filename matched by a
      pathname expansion pattern also matches one of the  patterns  in
      GLOBIGNORE, it is removed from the list of matches.

So, a simple way is to just set GLOBGINORE to the filename in question:

$ touch {a,b,c,d}.png
$ echo *.png
a.png b.png c.png d.png
$ echo *.png
a.png b.png d.png

So, in your case:

GLOBIGNORE=filename.gif; rm *.gif

Of course, since GLOBIGNORE contains patterns, you can use it whenever you want to exclude a pattern:

$ GLOBIGNORE='[a-c].png'; echo *.png
$ touch bd.png; GLOBIGNORE='?.png'; echo *.png

An advantage (?!) of GLOBIGNORE is that setting it automatically excludes . and .. from being matched, and it enables dotglob:

The  GLOBIGNORE shell variable may be used to restrict the set of file‐
names matching a pattern.  If GLOBIGNORE is set, each matching filename
that also matches one of the patterns in GLOBIGNORE is removed from the
list of matches.  The filenames ``.''  and ``..''  are  always  ignored
when  GLOBIGNORE is set and not null.  However, setting GLOBIGNORE to a
non-null value has the effect of enabling the dotglob shell option,  so
all other filenames beginning with a ``.''  will match.  To get the old
behavior of ignoring filenames beginning with a ``.'', make ``.*''  one
of  the  patterns  in  GLOBIGNORE.  The dotglob option is disabled when
GLOBIGNORE is unset.

So, a simple way of deleting all files and folders in a directory:

GLOBIGNORE=.; rm -r *

The * will match filenames beginning with ., since GLOBIGNORE enabled dotglob, but it won't match . or .., so you won't affect the parent directory that way.

share|improve this answer

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.