howto exclude files from wildcard matches in bash
Author: Udo Rader
creation date: 2003-08-24, last revision: 2003-11-26Imagine you have a directory that contains several hundreds of files and for what reason ever you want to list all files in this directory except one or even worse a couple of them.
This sounds easy, but actually isn't. Let me illustrate the situation:
bash$ ls -l /opt/too_many_files
secret.txt
top_secret.txt
confidential.txt
public.txt
secret.txt
top_secret.txt
confidential.txt
public.txt
Now say I want to get a listing of all files except confidential.txt. Of course, one could use sed or grep to do this but this is too much overhead as I don't want to waste resources.
There are a couple of solutions all with certain drawbacks:
Solution #1 - bad:
bash$ ls -l /opt/too_many_files/[c]*
secret.txt
top_secret.txt
public.txt
secret.txt
top_secret.txt
public.txt
The expected result. But this version only works, if you have no more than one file starting with 'c' in the directory. Otherwise all files starting with a 'c' will be omitted. On the other hand, this can be used for any command that uses file globbing (such as cp and mv).
Solution #2 - not so bad:
bash$ ls -l --ignore="confidential.txt" /opt/too_many_files
secret.txt
top_secret.txt
public.txt
secret.txt
top_secret.txt
public.txt
This gives us exactly the expected result. But however, this solution is bound to ls. If you want to do the same for cp or mv, you're stuck again, because those commands have no idea of the --ignore switch.
Solution #3 - good:
bash$ shopt -s extglob
bash$ ls -l /opt/too_many_files/!(confidential.txt)
secret.txt
top_secret.txt
public.txt
bash$ ls -l /opt/too_many_files/!(confidential.txt)
secret.txt
top_secret.txt
public.txt
This turns on bash'es extended globbing features (shopt -s extglob) and as a result you can use !(something) to exclude "something" from a file globbing operation, which is used by any command that uses wildcards (such as cp and mv again).
You may have noticed that '!' now has a different meaning (it is normally used to repeat old commands), which is its only drawback.
Solution #4 - best, IMHO:
bash$ GLOBIGNORE="confidential.txt"
bash$ ls -l /opt/too_many_files/*
secret.txt
top_secret.txt
public.txt
bash$ ls -l /opt/too_many_files/*
secret.txt
top_secret.txt
public.txt
GLOBIGNORE is a shell variable that allows to define certain patterns to identify things we don't want to be displayed whenever wildcards are used. So if I said "GLOBIGNORE="*secret*", secret.txt and top_secret.txt would not have been listed. And this is my™ favourite.
