bashwildcardglobcompgen

Bash compgen not parsing wildcard correctly


My goal is to list all files matching foo/** whereas this folder contains files (1 and 2) and subdirectories (bar) with files:

foo/
├── 1
└── bar
    └── 2

Bash has a build-in called compgen. I found out about this function in this answer. Using compgen -G <pattern>, in my case compgen -G 'foo/**' should list all files and folders. However, it only prints out the contents of the first folder (foo):

foo/bar
foo/1

-> foo/bar/2 is missing! I'd expect the following output:

foo/bar
foo/1
foo/bar/2

Is this a bug in bash's compgen or is the glob incorrect?


More experiments

$ compgen -G 'foo/**/*'
foo/bar/2

Using Python3's pathlib.Path.glob:

from pathlib import Path
p=Path(".").resolve()
[print(f) for f in p.glob("foo/**")]

Output:

foo
foo/bar

from pathlib import Path
p=Path(".").resolve()
[print(f) for f in p.glob("foo/**/*")]

Output:

foo/1
foo/bar
foo/bar/2

This version does exactly what I want, however, I'd prefer to minimize dependencies and not use Python at all (bash only).



Solution

  • compgen is a tool that exists specifically for generating completions. It does appear to be buggy (insofar as it doesn't honor the globstar option), but it's also not the best tool to use to generate a list of files matching a glob; so there's no real practical reason for that bug to be one anyone cares about.

    The best tool is, well, a glob expression.

    shopt -s extglob nullglob
    files=( foo/** ) # store files matching foo/** in an array
    echo "Found ${#files[@]} files:"
    printf ' - %q\n' "${files[@]}"
    

    If in your real-world environment you want to get the glob expression to expand from a variable, and only perform globbing and suppress string-splitting, then you'd want to temporarily set IFS to an empty value while performing the files=( $glob ) assignment.