from - Python glob pero contra una lista de cadenas en lugar del sistema de archivos
glob stock (5)
Quiero poder hacer coincidir un patrón en formato glob
con una lista de cadenas, en lugar de con archivos reales en el sistema de archivos. ¿Hay alguna manera de hacer esto, o convertir un patrón glob
fácilmente a una expresión regular?
El módulo glob
usa el módulo fnmatch
para elementos de ruta individuales .
Eso significa que la ruta se divide en el nombre del directorio y el nombre del archivo, y si el nombre del directorio contiene metacaracteres (contiene alguno de los caracteres [
, *
o ?
), Estos se expanden recursivamente .
Si tiene una lista de cadenas que son nombres de archivos simples, entonces basta con usar la función fnmatch.filter()
:
import fnmatch
matching = fnmatch.filter(filenames, pattern)
pero si contienen rutas completas, debe trabajar más, ya que la expresión regular generada no tiene en cuenta los segmentos de ruta (los comodines no excluyen los separadores ni se ajustan para la coincidencia de rutas entre plataformas).
Puedes construir un trie simple a partir de las rutas, luego hacer coincidir tu patrón con eso:
import fnmatch
import glob
import os.path
from itertools import product
# Cross-Python dictionary views on the keys
if hasattr(dict, ''viewkeys''):
# Python 2
def _viewkeys(d):
return d.viewkeys()
else:
# Python 3
def _viewkeys(d):
return d.keys()
def _in_trie(trie, path):
"""Determine if path is completely in trie"""
current = trie
for elem in path:
try:
current = current[elem]
except KeyError:
return False
return None in current
def find_matching_paths(paths, pattern):
"""Produce a list of paths that match the pattern.
* paths is a list of strings representing filesystem paths
* pattern is a glob pattern as supported by the fnmatch module
"""
if os.altsep: # normalise
pattern = pattern.replace(os.altsep, os.sep)
pattern = pattern.split(os.sep)
# build a trie out of path elements; efficiently search on prefixes
path_trie = {}
for path in paths:
if os.altsep: # normalise
path = path.replace(os.altsep, os.sep)
_, path = os.path.splitdrive(path)
elems = path.split(os.sep)
current = path_trie
for elem in elems:
current = current.setdefault(elem, {})
current.setdefault(None, None) # sentinel
matching = []
current_level = [path_trie]
for subpattern in pattern:
if not glob.has_magic(subpattern):
# plain element, element must be in the trie or there are
# 0 matches
if not any(subpattern in d for d in current_level):
return []
matching.append([subpattern])
current_level = [d[subpattern] for d in current_level if subpattern in d]
else:
# match all next levels in the trie that match the pattern
matched_names = fnmatch.filter({k for d in current_level for k in d}, subpattern)
if not matched_names:
# nothing found
return []
matching.append(matched_names)
current_level = [d[n] for d in current_level for n in _viewkeys(d) & set(matched_names)]
return [os.sep.join(p) for p in product(*matching)
if _in_trie(path_trie, p)]
Este bocado puede encontrar coincidencias rápidamente usando globos en cualquier lugar a lo largo del camino:
>>> paths = [''/foo/bar/baz'', ''/spam/eggs/baz'', ''/foo/bar/bar'']
>>> find_matching_paths(paths, ''/foo/bar/*'')
[''/foo/bar/baz'', ''/foo/bar/bar'']
>>> find_matching_paths(paths, ''/*/bar/b*'')
[''/foo/bar/baz'', ''/foo/bar/bar'']
>>> find_matching_paths(paths, ''/*/[be]*/b*'')
[''/foo/bar/baz'', ''/foo/bar/bar'', ''/spam/eggs/baz'']
En Python 3.4+ puedes usar PurePath.match
.
pathlib.PurePath(path_string).match(pattern)
En Python 3.3 o anterior (incluido 2.x), obtenga pathlib
de PyPI .
Tenga en cuenta que para obtener resultados independientes de la plataforma (que dependerán de la razón por la que esté ejecutando esto), querría indicar explícitamente PurePosixPath
o PureWindowsPath
.
Si bien fnmatch.fnmatch
se puede usar directamente para verificar si un patrón coincide con un nombre de archivo o no, también puede usar el método fnmatch.translate
para generar la expresión regular del patrón de fnmatch
dado:
>>> import fnmatch
>>> fnmatch.translate(''*.txt'')
''.*//.txt//Z(?ms)''
De la documenation :
fnmatch.translate(pattern)
Devuelve el patrón de estilo de shell convertido en una expresión regular.
no importa lo encontré. Quiero el módulo fnmatch .
Copia de buenos artistas; Los grandes artistas steal .
Robé ;)
fnmatch.translate
traduce globs ?
y *
a expresiones regulares .
y .*
respectivamente. Lo pellizqué para que no lo hiciera.
import re
def glob2re(pat):
"""Translate a shell PATTERN to a regular expression.
There is no way to quote meta-characters.
"""
i, n = 0, len(pat)
res = ''''
while i < n:
c = pat[i]
i = i+1
if c == ''*'':
#res = res + ''.*''
res = res + ''[^/]*''
elif c == ''?'':
#res = res + ''.''
res = res + ''[^/]''
elif c == ''['':
j = i
if j < n and pat[j] == ''!'':
j = j+1
if j < n and pat[j] == '']'':
j = j+1
while j < n and pat[j] != '']'':
j = j+1
if j >= n:
res = res + ''//[''
else:
stuff = pat[i:j].replace(''//',''////')
i = j+1
if stuff[0] == ''!'':
stuff = ''^'' + stuff[1:]
elif stuff[0] == ''^'':
stuff = ''//' + stuff
res = ''%s[%s]'' % (res, stuff)
else:
res = res + re.escape(c)
return res + ''/Z(?ms)''
Este uno a la fnmatch.filter
, tanto re.match
como re.search
trabajo.
def glob_filter(names,pat):
return (name for name in names if re.match(glob2re(pat),name))
Patrones y cadenas globales que se encuentran en esta página pasan la prueba.
pat_dict = {
''a/b/*/f.txt'': [''a/b/c/f.txt'', ''a/b/q/f.txt'', ''a/b/c/d/f.txt'',''a/b/c/d/e/f.txt''],
''/foo/bar/*'': [''/foo/bar/baz'', ''/spam/eggs/baz'', ''/foo/bar/bar''],
''/*/bar/b*'': [''/foo/bar/baz'', ''/foo/bar/bar''],
''/*/[be]*/b*'': [''/foo/bar/baz'', ''/foo/bar/bar''],
''/foo*/bar'': [''/foolicious/spamfantastic/bar'', ''/foolicious/bar'']
}
for pat in pat_dict:
print(''pattern :/t{}/nstrings :/t{}''.format(pat,pat_dict[pat]))
print(''matched :/t{}/n''.format(list(glob_filter(pat_dict[pat],pat))))