Я хочу иметь возможность сопоставить шаблон в glob
формате со списком строк, а не с фактическими файлами в файловой системе, Есть ли способ сделать это или легко преобразовать шаблон glob
в регулярное выражение?
Python glob, но против списка строк, а не файловой системы
Ответ 1
Хорошие художники копируют; великие художники steal.
Я украл;)
fnmatch.translate
переводит globs ?
и *
в regex .
и .*
соответственно. Я не потрогал это.
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)'
Эта одна аа fnmatch.filter
работает как re.match
, так и re.search
.
def glob_filter(names,pat):
return (name for name in names if re.match(glob2re(pat),name))
Шаблоны и строки Glob, найденные на этой странице, проходят тест.
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))))
Ответ 2
Модуль glob
использует модуль fnmatch
для отдельных элементов пути.
Это означает, что путь разбит на имя каталога и имя файла, а если имя каталога содержит метасимволы (содержит любые символы [
, *
или ?
), то они будут расширены рекурсивно.
Если у вас есть список строк, которые являются простыми именами файлов, достаточно просто использовать fnmatch.filter()
функцию:
import fnmatch
matching = fnmatch.filter(filenames, pattern)
но если они содержат полные пути, вам нужно больше работать, поскольку генерируемый регулярный выраженный элемент не учитывает сегменты маршрутов (маскировки не исключают разделители и не корректируются для соответствия межплатформенному пути).
Вы можете создать простой trie из путей, а затем сопоставить свой шаблон с этим:
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)]
Этот глоток может быстро найти совпадения с помощью глобусов в любом месте пути:
>>> 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']
Ответ 3
В Python 3.4+ вы можете просто использовать PurePath.match
.
pathlib.PurePath(path_string).match(pattern)
В Python 3.3 или более ранней версии (включая 2.x) получите pathlib
из PyPI.
Обратите внимание, что для получения результатов, не зависящих от платформы (которые будут зависеть от того, почему вы это используете), вы должны явно указать PurePosixPath
или PureWindowsPath
.
Ответ 4
Не думаю, я нашел его. Я хочу fnmatch модуль.
Ответ 5
В то время как fnmatch.fnmatch
можно использовать напрямую, чтобы проверить, соответствует ли шаблон имени файла или нет, вы также можете использовать метод fnmatch.translate
для генерации регулярного выражения из заданного шаблона fnmatch
:
>>> import fnmatch
>>> fnmatch.translate('*.txt')
'.*\\.txt\\Z(?ms)'
Из documenation:
fnmatch.translate(pattern)
Возвращает шаблон оболочки, преобразованный в регулярное выражение.