2011-07-27

GNU Make Recursive Wildcard Function

A reader wrote in to ask me about an old message in a mailing list where I'd defined a recursive version of GNU Make's wildcard function.

Unfortunately, there's an error in that message and the correct function is as follows:
rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d)) 
Usage is as follows. First, all C files in the current directory (or below).
$(call rwildcard,,*.c) 
Then all C files in /tmp:
$(call rwildcard,/tmp/,*.c) 
Multiple patterns can be used. Here are all C and H files:
$(call rwildcard,/tmp/,*.c *.h) 

13 comments:

Unknown said...

Thank you for posting this, I could not wrap my head around it myself.

Unknown said...

Thank you, this post saved me a lot of headaches.

Alex Cohn said...

Thanks a lot!

Alex Cohn said...

Thanks a lot!

Michel Grosjean said...

Many thanks for this, I was afraid I might have to do something very ugly, but thanks to you I guess I won't :D

Unknown said...

A minor change of the code makes it capabale of searching in a list of base directories:
rwildcard = $(foreach d,$(wildcard $(addsuffix *,$(1))),$(call rwildcard,$(d)/,$(2)) $(filter $(subst *,%,$(2)),$(d)))

The $(addsuffix *,$(1)) instead of $(1)* expands a list of directories in the root invocation into directory candidates for further procesing.

Just a tip: A $(strip ...) around the complete RHS makes it better maintainable in case you want to print the output.

test1111a said...

The archive is available at https://lists.gnu.org/archive/html/help-make/2006-03/msg00091.html

test1111a said...

I think that rwildcard should be aligned with original wildcard semantics which accepts the pure file name as pattern. Therefore the improved implementation can look like this:
$(strip $(foreach d,$(wildcard ${1}/*),$(call rwildcard,${d},${2}) $(filter $(subst %%,%,%$(subst *,%,${2})),${d})))

Vishwasu Deshpande said...

Is it possible to ignore certain file names within the desired file extension

John Graham-Cumming said...

Vishwasu: give me an example of what you want to do. I suspect that getting the output from the wildcard function and then running it through $(filter) or $(filter-out) will be the answer but happy to help.

Vishwasu Deshpande said...

Like I would wanted to grep all *.txt and then
1. Filter out files with some pattern in the file name, lets say
2. Also in the contents of the file, If I find certain pattern lets

John Graham-Cumming said...

Vishwasu,

You can find all the .txt files in /tmp (and below) like this:

TXT_FILES := $(call rwildcard,/tmp/*.txt)

If you then want to then filter on the filenames you can use $(filter) and $(filter-out) (see https://www.gnu.org/software/make/manual/html_node/Text-Functions.html). Or $(findstring).

If you then want to look at the contents of the files then I think you should use some other external program and call it using $(shell). You could do this in GNU Make but it's going to be messy and a lot of work. You'd have to do something like this to read the contents of a file:

CONTENTS := $(shell cat $(FILENAME))

Then use $(findstring) to look for the pattern you want and then use $(if) to make a decision on whether the pattern exists. You can certainly do that but GNU Make's pattern matching is quite limited and so I can see how you'd quickly run into trouble.

rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst\
*,%,$2),$d))

TXT_FILES := $(sort $(call rwildcard,/tmp,*.txt))
TXT_FILES_THAT_MATCH := $(foreach t,$(TXT_FILES),$(if $(findstring test,$t),$t)\
)

MATCHING_FILES := $(sort $(foreach f,$(TXT_FILES_THAT_MATCH),$(if $(findstring \
Jo,$(shell cat $f)),$f)))

$(info $(MATCHING_FILES))

Anonymous said...

Thanks , very helpful