2014-12-31 16 views
16

Chcę móc dopasować wzór w formacie glob do listy łańcuchów, a nie do rzeczywistych plików w systemie plików. Czy jest jakiś sposób to zrobić, lub przekonwertować wzór glob łatwo do regex?Python glob, ale z listą łańcuchów zamiast z systemem plików

+8

Dlaczego, do cholery, jest to tak naprawdę odrzucane? – Stilgar

+2

Czy możesz dodać więcej treści i kontekstu? Jak w oczekiwanym wyjściu dla fałszywego systemu plików –

+0

Więc chcesz podać listę plików jako python 'list' do globu, zamiast pobierania z systemu plików? –

Odpowiedz

7

Dobra kopia artysty; świetni artyści steal.

I stole;)

fnmatch.translate przekłada globs ? i * do REGEX . i .* odpowiednio. Poprawiłem go, aby nie.

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)' 

Ten à la fnmatch.filter zarówno re.match i re.search praca.

def glob_filter(names,pat): 
    return (name for name in names if re.match(glob2re(pat),name)) 

Wzorce globu i łańcuchy znalezione na tej stronie pomyślnie zaliczają test.

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)))) 
+0

bardzo wnikliwe - dzięki! –

+0

Świetna miarka! Tak, tłumaczenie wzoru na taki, który ignoruje separatory ścieżek, jest świetnym pomysłem. Zauważ, że nie obsługuje on 'os.sep' lub' os.altsep', ale powinno to być łatwe do przystosowania. –

+0

Dzięki @ martijn-pieters –

1

nieważne, znalazłem to. Chcę moduł fnmatch.

+0

Oh wait - fnmatch nie obsługuje segmentacji ścieżek ... westchnienie: –

+0

Czy możesz podać przykłady, w których 'fnmatch' nie obsługuje twojej sprawy? –

+0

@BhargavRao:' glob.glob() 'stosuje wzory do elementów ścieżek separetely –

1

Podczas fnmatch.fnmatch można stosować bezpośrednio w celu sprawdzenia, czy dany wzór pasuje do nazwy pliku lub nie, można również użyć metody fnmatch.translate do generowania wyrażenia regularnego z danym fnmatch wzoru:

>>> import fnmatch 
>>> fnmatch.translate('*.txt') 
'.*\\.txt\\Z(?ms)' 

Z documenation:

fnmatch.translate(pattern)

Powrót wzór shell-styl przekształcony do zwykłego ś ssion.

23

Moduł glob wykorzystuje fnmatch module dla poszczególne elementy ścieżki.

Oznacza to, że ścieżka jest podzielona na nazwę katalogu i nazwę pliku, a jeśli nazwa katalogu zawiera znaki meta (zawiera wszelkie znaki [, * lub ?) następnie są one rozbudowane rekurencyjnie.

Jeśli masz listę ciągów, które są proste nazwy plików, a potem po prostu stosując fnmatch.filter() function wystarczy:

import fnmatch 

matching = fnmatch.filter(filenames, pattern) 

ale jeśli zawierają pełne ścieżki, trzeba wykonać więcej pracy jako wyrażenie regularne generowane robi nie uwzględnia segmentów ścieżek (symbole wieloznaczne nie wykluczają separatorów ani nie są dostosowane do dopasowywania ścieżek między platformami).

można skonstruować prosty trie z torów, a następnie dopasować wzór przeciwko temu:

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)] 

ten kęs można szybko znaleźć mecze używając globs dowolnym miejscu wzdłuż ścieżki:

>>> 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

W Pythonie 3.4+ możesz po prostu użyć PurePath.match.

pathlib.PurePath(path_string).match(pattern) 

na Python 3.3 lub wcześniej (w tym 2.x), dostać pathlib from PyPI.

Należy pamiętać, że aby uzyskać wyniki niezależny od platformy (które zależą dlaczego używasz tego), że chcesz, aby wyraźnie stwierdzić PurePosixPath lub PureWindowsPath.

Powiązane problemy