When I use the code below in a terminal on Ubuntu, it works fine:

rm !(*.sh) -rf

But if I place the same line code in a shell script (clean.sh) and run shell script from terminal, it throws error as this

clean.sh script:

rm !(*.sh) -rf

Error I get:

./clean.sh: line 2: syntax error near unexpected token `('
./clean.sh: line 2: `rm !(*.sh) -rf'

can you help?


You need to turn extglob on:

shopt -s extglob

In bash, certain patterns are not enabled by default, but can be turned on by setting an option with shopt. The patterns !(NOT-THIS), @(THIS|THAT), *(ZERO-OR-MORE), +(ONE-OR-MORE) and ?(OPTIONAL), which didn't exist in early Unix shells, were later added in ksh, and aren't standardized by POSIX, are not recognized by default, you have to run shopt -s extglob first.

You can use these patterns on the command line; this indicates that you have shopt -s extglob in your .bashrc. That file is only read by interactive shells, not by scripts.

(If the environment variable BASH_ENV is set when bash starts running a script, then bash reads the file it designates, but I don't recommend relying on that, because it would make your script work only when BASH_ENV is set to the value that you expect, and only if you don't change the file it points to.)

Just include shopt -s extglob at the top of your script, below the #!/bin/bash line.


Shell Script Approach

By default, globs don't work in a BASH script (although you can turn them on with shopt). If the shell script ever gets run by a non-BASH interpreter, globs might not work at all.

You can get the same effect using the find command, which is how I'd recommend doing it (because of how much more control you can have once your requirements grow).

Try this on for size:
find . -mindepth 1 -maxdepth 1 -not -iname "*.sh" -exec rm -rf {} \;

Makefile Approach

Another approach you could take:

If you're doing something that wants to be cleaned afterwards, there's a good chance that a Makefile is the right tool for the job, rather than a bunch of clean.sh, build.sh, install.sh, etc.

This is especially true if you want to make sure your recipes always happen in order, or if you don't always want to re-run the recipes that produce an output.

A simple Makefile that did the same thing would look like this:
(note that the whitespace before rm needs to be a tab because that's how make rolls)

SOURCES := $(wildcard *.sh)
CLEAN_FILES := $(filter-out $(SOURCES),$(wildcard *))
CLEAN_FILES := $(filter-out Makefile,$(CLEAN_FILES))

build: $(SOURCES)

    rm -rf $(CLEAN_FILES)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.