2010-09-11 11 views
18

Windows utiliza nombres de archivo entre mayúsculas y minúsculas, por lo que puede abrir el mismo archivo con cualquiera de estos:En Python, ¿cómo puedo obtener la ruta correcta para un archivo?

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

, etc. indicados en cualquiera de estos caminos, ¿cómo puedo encontrar el verdadero caso? Quiero que todos ellos producen:

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

os.path.normcase no lo hace, simplemente minúsculas todo. os.path.abspath devuelve una ruta absoluta, pero cada una de ellas es absoluta, por lo que no cambia ninguna de ellas. os.path.realpath solo se usa para resolver enlaces simbólicos, que Windows no tiene, por lo que es lo mismo que abspath en Windows.

¿Hay una manera directa de hacerlo?

+1

Parece que este es un DUP de http://stackoverflow.com/questions/2113822/python-getting-filename-case -as-almacenados-en-ventanas, que tiene la respuesta. –

Respuesta

6

He aquí una sencilla, stdlib única solución:

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

Me gusta esto: ¡engaña a 'glob' para que haga' os.walk' por mí! –

+1

Se soluciona solo el nombre del archivo, no los subdires anteriores. Agrego otra respuesta, basada en este http://stackoverflow.com/a/14742779/1355726 – xvorsx

+0

Esto no parece funcionar. Además, fallarían los nombres de archivo con caracteres en ellos que son tokens glob. Si activó un escaneo de directorio, probablemente también sería patológicamente lento ... –

4

Dado que la definición de "verdadero caso" en sistemas de archivos NTFS (o VFAT) es realmente extraña, parece que la mejor manera sería caminar por el camino y hacer coincidir con os.listdir().

Sí, esto parece una solución artificial, pero también lo son las rutas NTFS. No tengo una máquina DOS para probar esto.

+0

Esta es la solución no directa a la que tenía miedo ... :( –

+0

+1 mis pensamientos son exactamente – aaronasterling

1

me gustaría utilizar os.walk, pero creo que por diskw con muchos directorios puede llevar mucho tiempo:

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 tiene una respuesta que no requiere de paquetes de terceros o recorrer el árbol:

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

Esto no funciona para mí (probando en W7) – Joril

+0

Puede fallar porque 'ruta' debe ser unicode para' GetLongPathNameW '.Trate de reemplazar' ruta' en la llamada 'GetLongPathName (ruta, buf, 260)' con 'unicode (ruta)'. – Attila

+0

Esto no funciona. GetLongPathName solo expande nombres de archivos cortos, así que si le das "C: \ Progra ~ 1 "obtendrá" C: \ Archivos de programa ", pero si le da" C: \ PROGRAM FILES ", ya es un largo camino para que no lo cambie. –

13

Ned's GetLongPathName answer does not quite work (al menos no para mí). Debe llamar al GetLongPathName en el valor de retorno de GetShortPathname. Usando pywin32 por razones de brevedad (una solución ctypes tendría un aspecto similar a Ned):

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

Funciona perfectamente, gracias :) – Joril

+0

Ver mi comentario a http://stackoverflow.com/a/2114975/179715; esto no está garantizado para funcionar si la generación corta de nombre de archivo está deshabilitada. – jamesdlin

+0

Si le importa la ruta completa, tenga en cuenta que esto no convertirá una letra de unidad en la mayúscula más típica. –

6

Ethan answer correcta sólo el nombre de archivo, nombres no subcarpetas en el camino . Aquí es mi suposición:

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

Yo prefiero el enfoque de Ethan y xvorsx. Que yo sepa, los siguientes no sería también daño en otras plataformas:

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

Basado de un par de la listdir/caminar ejemplos anteriores, pero apoya rutas 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

sólo estaba luchando con la misma problema. No estoy seguro, pero creo que las respuestas anteriores no cubren todos los casos. Mi problema real era que la carcasa de la letra de unidad era diferente a la vista por el sistema.Aquí está mi solución que también comprueba la carcasa letra correcta (usando 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

Éste unifica, acorta y corrige varios enfoques: lib estándar solamente; convierte todas las partes de ruta (excepto letra de unidad); caminos relativos o absolutos; maneje con letra o no; tolarant:

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

Y éste maneja rutas UNC, además:

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

Este es el único en este hilo que funcionó para mí. ¡Gracias! – MaVCArt

Cuestiones relacionadas