Exclude one pattern from glob match

I have several files with the same base filename. I’d like to remove all but one

foo.org #keep
foo.tex #delete
foo.fls #delete
foo.bib #delete

If I didn’t need to keep one, I know I could use rm foo.*.

TLDP demonstrates ^ to negate a match. Through trial and error, I was able to find that

rm foo.*[^org]

does what I need, but I don’t really understand the syntax.

Also, while not a limitation in my use case, I think this pattern also ignores foo.o and foo.or. How does this pattern work, and what would a glob that ignores only foo.org look like?

Asked By: jake

shopt -s extglob
echo rm foo.!(org)

This matches foo. followed by anything NOT org

To restore normal glob behavior afterwards, use:

shopt -u extglob

Reference: The GNU Bash reference manual

Answered By: glenn jackman

In bash, you can also use GLOBIGNORE="*.org"; rm -i foo*.

And unset GLOBIGNORE when done.

It’s not really better than shopt -s extglob, but I find it easier to remember.

Answered By: mivk

A pipe can do?

ls * | grep -v "foo.org" | xargs -I {} echo {}

(obviously you might want to replace echo with rm in the last chain).

Answered By: Adobe

Another way to accomplish. Suppose that you have some file foo.sh that you want exclude from your operation, yet you want to add csv to every other file. You could use the following loop:

for f in *; do
  if [ "${f: -3}" != ".sh" ]; then
    mv "$f" "${f%}.csv"
Answered By: GigaWatts

The pattern foo.*[^org], which is properly written foo.*[!org] (but bash can use ^ in place of !) would match any name that starts with foo. and ends in something that is not o, r or g. The [...] bit always matches exactly one single character. This would not match foo.org as it ends with g, but it would also not match foo.log for the same reason.

A portable answer would be to use a loop, and then to avoid acting on the file that we want to keep:

for name in foo.*; do [ "$name" != foo.org ] && rm -- "$name"; done

If you want to avoid deleting any file matching foo.* and ending in .org (e.g. foo.org or foo.beef.org etc.), then consider using a second pattern match like so:

for name in foo.*; do case $name in (*.org) continue;; esac; rm -- "$name"; done

Using find instead, the first loop above would be equivalent to

find . ! -path . -prune -name 'foo.*' ! -name foo.org -exec rm {} +

while the second one would be the same as

find . ! -path . -prune -name 'foo.*' ! -name '*.org' -exec rm {} +
Answered By: Kusalananda
Categories: Answers Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.