I would like to recursively list all files in a directory which match a given input pattern using ls. (Oh, and this is in Bash).

The only way I can think of to do this using ls is to shopt -s extglob and then use the ls --ignore=pattern option with the extended glob negate pattern !( pattern-list ). Seems kind of roundabout to negate in an ignore but otherwise wildcards don't recurse.

Assuming I already have the directory to look in as dir and the pattern to match against as file, this is my current attempt:

ls -AR --ignore='!('"${file}"')' ${dir}

However, this does not seem to have the desired effect, for example in a test case where the directory structure gives this output for ls -AR:

Great suggestion, I didn't know they added globstar. However, since I am looking to match the input pattern (which wasn't clear, fixed that), not omit it I think I need to use ls -dAR **/*.c.
–
suess2.0Feb 13 '13 at 22:01

1

Note that contrary to zsh (where that feature originated) and ksh93 (where bash copied it from), bash's ** follows symbolic links when descending the directory tree, which can have serious unwanted effects. So use with care or use find or other shells if there's a risk that there be symlinks in the directory.
–
Stéphane ChazelasFeb 13 '13 at 22:10

@choroba, the -A and -R options don't make sense in combination with -d and can be removed (the shell makes up the list of files here, not ls, ls in that case is just outputting the list so could be replaced with printf '%s\n'). Also, you need -- to mark the end of options or write it ls -dAR ./**/!(*.c) to avoid problems with filenames starting with -. Also, if there's no c file and if there's a directory called ** and a file called !(*.c) inside it, ls will report it even though it's not a c file. You may want to add the failglob option.
–
Stéphane ChazelasFeb 13 '13 at 22:34

1

The symlink issue has now been fixed in bash-4.3
–
Stéphane ChazelasMar 18 '14 at 13:45

In ls -AR --ignore=!(*.c), since the pattern is not quoted, bash would try to expand it before passing it to ls. So it would look in the current directory for files whose name starts with "--ignore=" and doesn't end in ".c". For instance, if there were two files, one called --ignore=foo and one called --ignore=bar, it would run ls with those four arguments: "ls", "-AR", "--ignore=bar", "--ignore=foo".

You can do (with GNU find).

find . -regextype posix-extended -regex '.*\.([ch]|cpp)' -ls

Or with any POSIX find:

find . \( -name '*.[ch]' -o -name '*.cpp' \) -exec ls -ld {} +

Or assuming there's no symlinks to directories:

shopt -s extglob globstar dotglob failglob
ls -ld -- **/*.@([ch]|cpp)

This time, bash does expand the globbing pattern and passes the list of files to ls.