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
.
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 communityHere'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"
fi
done
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
.
find
, which is highly versatile.
Feb 15, 2013 at 18:59
find
is indeed a good find.
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.
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".
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.
Feb 18, 2013 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.)
There's the GLOBIGNORE
variable. From man bash
:
GLOBIGNORE
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
$ GLOBIGNORE=c.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
d.png
$ touch bd.png; GLOBIGNORE='?.png'; echo *.png
bd.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.