2010-09-11 11 views
18

Windows utilizza i nomi di file case-insensitive, quindi posso aprire lo stesso file con uno di questi:In Python, come posso ottenere il percorso corretto per un file?

r"c:\windows\system32\desktop.ini" 
r"C:\WINdows\System32\DESKTOP.ini" 
r"C:\WiNdOwS\SyStEm32\DeSkToP.iNi" 

ecc Dato qualsiasi di questi percorsi, come posso trovare il vero caso? Li voglio tutti a produrre:

r"C:\Windows\System32\desktop.ini" 

os.path.normcase non lo fa, è solo caratteri minuscoli semplicemente tutto. os.path.abspath restituisce un percorso assoluto, ma ognuno di questi è già assoluto e quindi non cambia nessuno di essi. os.path.realpath viene utilizzato solo per risolvere i collegamenti simbolici, che Windows non ha, quindi è lo stesso di abspath su Windows.

C'è un modo semplice per farlo?

+1

Sembra che questo è un duplicato di http://stackoverflow.com/questions/2113822/python-getting-filename-case -as-stored-in-windows, che ha la risposta. –

risposta

6

Ecco un semplice, stdlib unica soluzione:

import glob 
def get_actual_filename(name): 
    name = "%s[%s]" % (name[:-1], name[-1]) 
    return glob.glob(name)[0] 
+1

Mi piace questo: inganna 'glob' nel fare il' os.walk' per me! –

+1

Corregge solo il nome del file, non i sottodirectory precedenti. Aggiungo un'altra risposta, basata su questo http://stackoverflow.com/a/14742779/1355726 – xvorsx

+0

Questo non sembra funzionare. Inoltre, fallirebbe per i nomi di file con caratteri in essi che sono token glob. Se ha fatto scattare una scansione della directory, probabilmente sarebbe anche patologicamente lento ... –

4

Poiché la definizione di "true case" su file system NTFS (o VFAT) è davvero bizzarra, sembra che il modo migliore sarebbe quello di percorrere il percorso e confrontarsi con os.listdir().

Sì, questa sembra una soluzione forzata, ma lo sono anche i percorsi NTFS. Non ho una macchina DOS per testarlo.

+0

Questa è la soluzione non semplice di cui avevo paura ... :( –

+0

+1 i miei pensieri esattamente – aaronasterling

1

userei os.walk, ma penso che per diskw con molti indici può richiedere molto tempo:

fname = "g:\\miCHal\\ZzZ.tXt" 
if not os.path.exists(fname): 
    print('No such file') 
else: 
    d, f = os.path.split(fname) 
    dl = d.lower() 
    fl = f.lower() 
    for root, dirs, files in os.walk('g:\\'): 
     if root.lower() == dl: 
      fn = [n for n in files if n.lower() == fl][0] 
      print(os.path.join(root, fn)) 
      break 
7

This python-win32 thread ha una risposta che non richiede pacchetti di terze parti o camminare l'albero:

import ctypes 

def getLongPathName(path): 
    buf = ctypes.create_unicode_buffer(260) 
    GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW 
    rv = GetLongPathName(path, buf, 260) 
    if rv == 0 or rv > 260: 
     return path 
    else: 
     return buf.value 
+1

Questo non funziona per me (test su W7) – Joril

+0

Potrebbe non funzionare perché 'percorso' deve essere unicode per' GetLongPathNameW 'Prova a sostituire' percorso' nella chiamata 'GetLongPathName (path, buf, 260)' con 'unicode (path)'. – Attila

+0

Questo non funziona. GetLongPathName espande solo i nomi brevi, quindi se gli dai "C: \ Progra ~ 1 "riceverai" C: \ Programmi ", ma se lo dai" C: \ PROGRAM FILES ", è già un percorso lungo, quindi non lo cambierà –

13

La risposta di Ned GetLongPathName non funziona (almeno non per me). È necessario chiamare GetLongPathName sul valore restituito di GetShortPathname. Utilizzando pywin32 per brevità (una soluzione ctypes sarebbe simile a Ned):

>>> win32api.GetLongPathName(win32api.GetShortPathName('stopservices.vbs')) 
'StopServices.vbs' 
+0

Funziona perfettamente, grazie :) – Joril

+0

Vedere il mio commento su http://stackoverflow.com/a/2114975/179715; questo non è garantito per funzionare se la generazione di un nome breve è disabilitata. – jamesdlin

+0

Se ci si preoccupa del percorso completo, si noti che questo non converte una lettera di unità nella più tipica maiuscola. –

6

Ethan answer file corretto solo il nome, nomi non sottocartelle sul percorso . Ecco la mia ipotesi:

def get_actual_filename(name): 
    dirs = name.split('\\') 
    # disk letter 
    test_name = [dirs[0].upper()] 
    for d in dirs[1:]: 
     test_name += ["%s[%s]" % (d[:-1], d[-1])] 
    res = glob.glob('\\'.join(test_name)) 
    if not res: 
     #File not found 
     return None 
    return res[0] 
2

Io preferisco l'approccio di Ethan e xvorsx. Per quanto ne so, quanto segue non avrebbe fatto del male anche su altre piattaforme:

import os.path 
from glob import glob 

def get_actual_filename(name): 
    sep = os.path.sep 
    parts = os.path.normpath(name).split(sep) 
    dirs = parts[0:-1] 
    filename = parts[-1] 
    if dirs[0] == os.path.splitdrive(name)[0]: 
     test_name = [dirs[0].upper()] 
    else: 
     test_name = [sep + dirs[0]] 
    for d in dirs[1:]: 
     test_name += ["%s[%s]" % (d[:-1], d[-1])] 
    path = glob(sep.join(test_name))[0] 
    res = glob(sep.join((path, filename))) 
    if not res: 
     #File not found 
     return None 
    return res[0] 
2

in base a un paio di listdir/camminare esempi precedenti, ma supporta percorsi UNC

def get_actual_filename(path): 
    orig_path = path 
    path = os.path.normpath(path) 

    # Build root to start searching from. Different for unc paths. 
    if path.startswith(r'\\'): 
     path = path.lstrip(r'\\') 
     path_split = path.split('\\') 
     # listdir doesn't work on just the machine name 
     if len(path_split) < 3: 
      return orig_path 
     test_path = r'\\{}\{}'.format(path_split[0], path_split[1]) 
     start = 2 
    else: 
     path_split = path.split('\\') 
     test_path = path_split[0] + '\\' 
     start = 1 

    for i in range(start, len(path_split)): 
     part = path_split[i] 
     if os.path.isdir(test_path): 
      for name in os.listdir(test_path): 
       if name.lower() == part.lower(): 
        part = name 
        break 
      test_path = os.path.join(test_path, part) 
     else: 
      return orig_path 
    return test_path 
1

ero solo alle prese con lo stesso problema. Non ne sono sicuro, ma penso che le risposte precedenti non coprano tutti i casi. Il mio vero problema era che l'alloggiamento della lettera di unità era diverso da quello visto dal sistema.Ecco la mia soluzione che controlla anche per l'involucro lettera di unità corretta (utilizzando Win32API):

def get_case_sensitive_path(path): 
     """ 
     Get case sensitive path based on not - case sensitive path. 

     Returns: 
     The real absolute path. 

     Exceptions: 
     ValueError if the path doesn't exist. 

     Important note on Windows: when starting command line using 
     letter cases different from the actual casing of the files/directories, 
     the interpreter will use the invalid cases in path (e. g. os.getcwd() 
     returns path that has cases different from actuals). 
     When using tools that are case - sensitive, this will cause a problem. 
     Below code is used to get path with exact the same casing as the 
     actual. 
     See http://stackoverflow.com/questions/2113822/python-getting-filename-case-as-stored-in-windows 
     """ 
     drive, path = os.path.splitdrive(os.path.abspath(path)) 
     path = path.lstrip(os.sep) 
     path = path.rstrip(os.sep) 
     folders = [] 

     # Make sure the drive number is also in the correct casing. 
     drives = win32api.GetLogicalDriveStrings() 
     drives = drives.split("\000")[:-1] 
     # Get the list of the the form C:, d:, E: etc. 
     drives = [d.replace("\\", "") for d in drives] 
     # Now get a lower case version for comparison. 
     drives_l = [d.lower() for d in drives] 
     # Find the index of matching item. 
     idx = drives_l.index(drive.lower()) 
     # Get the drive letter with the correct casing. 
     drive = drives[idx] 

     # Divide path into components. 
     while 1: 
      path, folder = os.path.split(path) 
      if folder != "": 
       folders.append(folder) 
      else: 
       if path != "": 
        folders.append(path) 
       break 

     # Restore their original order. 
     folders.reverse() 

     if len(folders) > 0: 
      retval = drive + os.sep 

      for folder in folders: 
       found = False 
       for item in os.listdir(retval): 
        if item.lower() == folder.lower(): 
         found = True 
         retval = os.path.join(retval, item) 
         break 
       if not found: 
        raise ValueError("Path not found: '{0}'".format(retval)) 

     else: 
      retval = drive + os.sep 

     return retval 
5

Questo unifica, si accorcia e corregge diversi approcci: lib standard unico; converte tutte le parti del percorso (tranne la lettera di unità); percorsi relativi o assoluti; guidare letterato o no; tolarant:

def casedpath(path): 
    r = glob.glob(re.sub(r'([^:/\\])(?=[/\\]|$)', r'[\1]', path)) 
    return r and r[0] or path 

E questo maniglie percorsi UNC in aggiunta:

def casedpath_unc(path): 
    unc, p = os.path.splitunc(path) 
    r = glob.glob(unc + re.sub(r'([^:/\\])(?=[/\\]|$)', r'[\1]', p)) 
    return r and r[0] or path 
+0

Questo è l'unico in questa discussione che ha funzionato per me. Grazie! – MaVCArt