Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

fnmatch and recursive path match with `**`

Is there any built-in or straightforward way to match paths recursively with double asterisk, e.g. like zsh does?

For example, with

path = 'foo/bar/ham/spam/eggs.py'

I can use fnmatch to test it with

fnmatch(path, 'foo/bar/ham/*/*.py'

Although, I would like to be able to do:

fnmatch(path, 'foo/**/*.py')

I know that fnmatch maps its pattern to regex, so in the words case I can roll my own fnmatch with additional ** pattern, but maybe there is an easier way

like image 689
Jakub M. Avatar asked Aug 20 '13 17:08

Jakub M.


2 Answers

If you look into fnmatch source code closely, it internally converts the pattern to a regular expression, mapping * into .* (and not [^/]* or similar) and thus does not care anything for directory separators / - unlike UNIX shells:

while i < n:
    c = pat[i]
    i = i+1
    if c == '*':
        res = res + '.*'
    elif c == '?':
        res = res + '.'
    elif c == '[':
        ...

Thus

>>> fnmatch.fnmatch('a/b/d/c', 'a/*/c')
True
>>> fnmatch.fnmatch('a/b/d/c', 'a/*************c')
True
like image 179

For an fnmatch variant that works on paths, you can use a library called wcmatch which implements a globmatch function that matches a path with the same logic that glob crawls a filesystem with. You can control the enabled features with flags, in this case, we enable GLOBSTAR (using ** for recursive directory search).

>>> from wcmatch import glob
>>> glob.globmatch('some/file/path/filename.txt', 'some/**/*.txt', flags=glob.GLOBSTAR)
True
like image 31
facelessuser Avatar answered Oct 24 '22 19:10

facelessuser