1

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:

    .:
    a  a.c  b  b.c  c  c.h  s

    ./s:
    a  a.c  b  b.c  c  c.h

My command with file = *.c and dir = s (full command: ls -AR --ignore=!(*.c) s) gives this output:

    ./s:
    a  a.c  b  b.c  c  c.h

I know I could pipe it into grep quite easily, and do something like ls -AR s | grep -v '.*.c' and get output like:

    s:
    a
    b
    c
    c.h

but I am looking for a way to do this just through ls.

Sorry if I was unclear at all, this is my first time on a StackExchange site.

Thanks in advance!

2 Answers 2

3

What about adding the globstar shell option and the -d flag to ls to not automatically list directory contents?

shopt -s extglob
shopt -s globstar
ls -d  **/!(*.c)
4
  • 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.
    – samebertz
    Feb 13, 2013 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. Feb 13, 2013 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. Feb 13, 2013 at 22:34
  • 1
    The symlink issue has now been fixed in bash-4.3 Mar 18, 2014 at 13:45
2

GNU ls --ignore option takes standard file name patterns, not a ksh extended one (what bash (and bash only, not ls) recognises with extglob)

So you can do:

ls -AR --ignore='*.[ch]' --ignore='*.cpp'

But you can't do:

ls -AR --ignore='!(*.[ch]|*.cpp)'

Which would just ignore files named like: !(foo.c|bar.cpp).

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.

1
  • Thanks for the extra information, that explains extglob nicely.
    – samebertz
    Feb 13, 2013 at 22:07

You must log in to answer this question.

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