2014-12-31 13 views
16

Desidero poter abbinare un modello nel formato glob a un elenco di stringhe piuttosto che a file effettivi nel filesystem. C'è un modo per farlo o convertire facilmente un modello glob in un'espressione regolare?Glob python ma rispetto a un elenco di stringhe anziché al filesystem

+8

Perché diavolo è davvero downvoted? – Stilgar

+2

Puoi aggiungere più contenuto e contesto per favore? Come nell'output atteso per un file system fittizio –

+0

Quindi vuoi dare un elenco di file come python 'list' a glob, invece di prelevarlo dal filesystem? –

risposta

7

I buoni artisti copiano; grandi artisti steal.

Ho vinto;)

fnmatch.translate traduce gocce ? e * per RegEx . e .* rispettivamente. L'ho ottimizzato per non farlo.

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

Questo à la fnmatch.filter, sia re.match e re.search lavoro.

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

modelli Glob e le stringhe trovate su questa pagina di prova Pass.

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

molto perspicace - grazie! –

+0

Grande scoop! Sì, tradurre il modello in uno che ignora i separatori di percorsi è una grande idea. Nota che non gestisce 'os.sep' o' os.altsep', ma dovrebbe essere abbastanza facile da regolare per quello. –

+0

Grazie a @ martijn-pieters –

1

non importa, l'ho trovato. Voglio il modulo fnmatch.

+0

Oh wait - fnmatch non gestisce la segmentazione del percorso ... sigh –

+0

Puoi fornire esempi, dove 'fnmatch' non gestisce il tuo caso? –

+0

@BhargavRao:' glob.glob() 'applica i tracciati agli elementi del percorso separatamente –

1

Mentre fnmatch.fnmatch può essere utilizzato direttamente per verificare se un modello corrisponde a un nome di file o no, è anche possibile utilizzare il metodo fnmatch.translate per generare la regex fuori del data fnmatch modello:

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

Dal documenation:

fnmatch.translate(pattern)

Ritorna il modello della shell in stile convertito in un'espres regolare fissione.

23

Il modulo glob utilizza le fnmatch module per singoli elementi di percorso.

Ciò significa che il percorso è suddiviso nel nome della directory e il nome del file, e se il nome della directory contiene caratteri meta (contiene uno dei caratteri [, * o ?) allora questi vengono espanse ricorsivamente.

Se si dispone di una lista di stringhe che sono semplici nomi di file, quindi solo utilizzando il fnmatch.filter() function è sufficiente:

import fnmatch 

matching = fnmatch.filter(filenames, pattern) 

ma se contengono percorsi completi, è necessario lavorare di più come l'espressione regolare generato doesn t prendere in considerazione i segmenti di percorso (i caratteri jolly non escludono i separatori e non vengono modificati per la corrispondenza del percorso multipiattaforma).

È possibile costruire un semplice trie dai sentieri, quindi abbinare il vostro modello contro che:

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

Questo boccone può rapidamente trovare corrispondenze con gocce ovunque lungo il percorso:

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

su Python 3.4+ puoi semplicemente usare PurePath.match.

pathlib.PurePath(path_string).match(pattern) 

su Python 3.3 o precedenti (compresa la 2.x), ottenere pathlib from PyPI.

Si noti che per ottenere risultati indipendenti dalla piattaforma (che dipenderanno perché si sta eseguendo questo) che ci si vuole dichiarare esplicitamente PurePosixPath o PureWindowsPath.