Подтвердить что ты не робот

Python glob, но против списка строк, а не файловой системы

Я хочу иметь возможность сопоставить шаблон в glob формате со списком строк, а не с фактическими файлами в файловой системе, Есть ли способ сделать это или легко преобразовать шаблон glob в регулярное выражение?

4b9b3361

Ответ 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)

Возвращает шаблон оболочки, преобразованный в регулярное выражение.