Search code examples
bashshellglob

Shell globbing exclude directory patterns


Disregarding commands that can be used in association with shell globs, I would like to accomplish excluding specific directory patterns across all directories and sub-directories from a glob pattern.

Let's say I have this directory structure:

+-- a
    +-- a1
         +-- assets
                  +-- a1-1
                         +-- a1-1.js
                  +-- a1-2
                         +-- a1-2.js
    +-- a2
         +-- a2.js
+-- b
    +-- b1
         +-- b1-2
                +-- b1-2-3
                         +-- assets
                                  +-- b1-2-3.js
    +-- b.js
+-- c
    +-- c.js

I would like to list all js files that do not have assets in their file paths.

What I've tried so far:

$ shopt -s globstar extglob
$ ls ./**/!(assets)/**/*.js

Not only did the pattern above achieve its goal, it even shows duplicate outputs. I am aware that I can do something like this:

$ ls ./**/*.js | grep -v "assets"

Or any other commands that can be piped, I simply want a pure shell glob pattern.

Expected Output:

a/a2/a2.js  
b/b.js  
c/c.js

Solution

  • Michael's answer is right. ** matches too much (greedy match), including assets.

    So, with this tree:

    .
    |-- a
    |   |-- a1
    |   |   +-- assets
    |   |       |-- a1-1
    |   |       |   +-- a1-1.js
    |   |       +-- a1-2
    |   |           +-- a1-2.js
    |   +-- a2
    |       +-- a2.js
    |-- assets
    |   +-- xyz.js
    |-- b
    |   |-- b1
    |   |   +-- b1-2
    |   |       +-- b1-2-3
    |   |           |-- assets
    |   |           |   +-- b1-2-3.js
    |   |           +-- test
    |   |               |-- test2
    |   |               |   +-- test3
    |   |               |       +-- test4
    |   |               |           +-- test4.js
    |   |               +-- test.js
    |   +-- b.js
    |-- c
    |   +-- c.js
    +-- x.js
    

    The .js files are:

    $ find . -name '*.js'
    ./x.js
    ./assets/xyz.js
    ./a/a2/a2.js
    ./a/a1/assets/a1-2/a1-2.js
    ./a/a1/assets/a1-1/a1-1.js
    ./c/c.js
    ./b/b.js
    ./b/b1/b1-2/b1-2-3/test/test2/test3/test4/test4.js
    ./b/b1/b1-2/b1-2-3/test/test.js
    ./b/b1/b1-2/b1-2-3/assets/b1-2-3.js
    

    There is a bash variable GLOBIGNORE to do exactly what you are trying to do.

    So, this would work:

    $ GLOBIGNORE='**/assets/**:assets/**:**/assets'
    $ ls -1 **/*.js
    a/a2/a2.js
    b/b1/b1-2/b1-2-3/test/test2/test3/test4/test4.js
    b/b1/b1-2/b1-2-3/test/test.js
    b/b.js
    c/c.js
    x.js