9

In the script it reads:

rm -v !\(*.yaml\) ;\

this produces

rm: cannot remove '!(*.yaml)': No such file or directory

but works fine in the command line. Have tried escaping in various ways:

'\!\(\*.yaml\)'
`\!\(\*.yaml\)`
`!\(*.yaml\"\)`
"\!\(\*.yaml\)"

Can't seem to figure out the appropriate escape sequence, I simply don't understand. Escaping the brackets was my first step. Then trying to escape the !, then the *. Also tried no escaping, using back-ticks but I got the error "rm missing operand". Im a little stumped. Have been at it for about an hour - just "rm everything not yaml"... Can anyone perhaps spot the error/suggest a fix?

I have also tried #!/bin/sh and #!/bin/bash. Thought it would maybe have some effect.

1
  • Presumably your Linux distro's dotfiles run shopt -s extglob by default in interactive shells -- without a distro changing the defaults, it's normally off for interactive shells too. Commented Sep 11, 2022 at 20:26

3 Answers 3

19

!(x) is a Korn shell glob operator. bash supports a subset of ksh's globs operators including that one but only after shopt -s extglob¹

So:

shopt -s extglob
rm -f -- !(*.yaml)

Will remove all the non-hidden files in the current directory except those whose name ends in .yaml.

In any case, glob operators are not recognised as such when quoted, so using \ (one of the quoting operators in Bourne-like shells) won't help.

The equivalent in the zsh shell would be:

rm -f -- ^*.yaml

For which you need set -o extendedglob.

(it does also support !(x) after emulate ksh or set -o kshglob).

There is no equivalent in the sh language, though you may find that some sh implementations, like those based on ksh support !(x) as an extension.


¹ as that affects the shell syntax parsing, the shopt command must have been executed before a line containing one of those ksh extended operators is read and parsed. For instance in bash -c 'shopt -s extglob; echo !(x)', you get a syntax error because the whole line is parsed first (with extglob not on yet), and then shopt is run (well, would have been run if not for the syntax error). bash -O extglob -c 'echo !(x)' is fine.

0
10

Bash has a feature where the variable GLOBIGNORE can tell the shell what filenames to remove from a glob, leaving the rest to be operated on (in this case with rm -v).

Here's a demonstration:

mkdir /tmp/this-test
cd /tmp/this-test
touch one.yaml two.yaml three.json four.txt
echo *

Produces:

four.txt one.yaml three.json two.yaml

Now set GLOBIGNORE to ignore the .yaml files, and then disable its effect:

GLOBIGNORE='*.yaml'
echo *
GLOBIGNORE=
echo *

Produces:

four.txt three.json
four.txt one.yaml three.json two.yaml

The command rm -v * instead of echo * would remove the files in the directory whose names don't end in .yaml.

GLOBIGNORE can have a list of glob expressions, each separated by a colon (:). For example if I had set GLOBIGNORE='*.yaml:*.json' in the above demonstration, that GLOBIGNORE value would have removed the .yaml and .json filenames from the glob list, so only the four.txt file would have been displayed or deleted.

3

An alternative approach combining find & rm ...

starting with a folder containing five files

cd /path/to/files/
touch one.yaml two.yaml three.txt four.yaml five.json

you can use the utility find, that allows the exclusion of names patterns, however to scan non-recursively with find you'll need to set -mindepth and -maxdepth to 1.

find /path/to/files -mindepth 1 -maxdepth 1 -type f ! -name '*.yaml' -exec rm -v '{}' \;
removed '/path/to/files/three.txt'
removed '/path/to/files/five.json'

then you can use rm -v individually on every file found. This might be slow on huge numbers of files as it separately invokes rm on every file.

However you can also use -delete to get the same effect, without the verbose output, but will be faster.

find /path/to/files -mindepth 1 -maxdepth 1 -type f ! -name '*.yaml' -delete
2
  • 2
    You can end that last command with -print -delete to see the files as they're deleted. -print alone is just the default if no action was given, which is why -delete alone doesn't print anything.
    – Izkata
    Commented Sep 12, 2022 at 21:42
  • @Izkata - thank you, I always forget you can combine -print and -delete (+1)
    – MNB
    Commented Sep 13, 2022 at 23:11

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .