En Python, ¿cómo puedo obtener la ruta de acceso correcta para un archivo?
title python (10)
Windows usa nombres de archivo que no distinguen entre mayúsculas y minúsculas, por lo que puedo abrir el mismo archivo con cualquiera de estos:
r"c:/windows/system32/desktop.ini"
r"C:/WINdows/System32/DESKTOP.ini"
r"C:/WiNdOwS/SyStEm32/DeSkToP.iNi"
etc. Dado cualquiera de estos caminos, ¿cómo puedo encontrar el caso verdadero? Quiero que todos produzcan:
r"C:/Windows/System32/desktop.ini"
os.path.normcase
no lo hace, simplemente hace minúsculas. os.path.abspath
devuelve una ruta absoluta, pero cada una de ellas ya es absoluta, por lo que no cambia ninguna de ellas. os.path.realpath
solo se utiliza para resolver enlaces simbólicos, que Windows no tiene, por lo que es lo mismo que abspath en Windows.
¿Hay una manera sencilla de hacer esto?
Éste unifica, acorta y corrige varios enfoques: solo biblioteca estándar; convierte todas las partes de ruta (excepto la letra de unidad); caminos relativos o absolutos; letra de unidad o no; tolarante
def casedpath(path):
r = glob.glob(re.sub(r''([^:///])(?=[///]|$)'', r''[/1]'', path))
return r and r[0] or path
Y éste maneja caminos 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
Aquí hay una solución simple, solo estándar,
import glob
def get_actual_filename(name):
name = "%s[%s]" % (name[:-1], name[-1])
return glob.glob(name)[0]
Basado en un par de los ejemplos listdir / walk anteriores, pero admite 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
Dado que la definición de "caso real" en los sistemas de archivos NTFS (o VFAT) es realmente extraña, parece que la mejor manera sería recorrer la ruta y os.listdir() con os.listdir() .
Sí, esto parece una solución artificial, pero también lo son las rutas NTFS. No tengo una máquina de DOS para probar esto.
La respuesta de GetLongPathName
de Ned no funciona del todo (al menos no para mí). GetLongPathName
llamar a GetLongPathName
en el valor de retorno de GetShortPathname
. Usando pywin32 por brevedad (una solución de ctypes sería similar a la de Ned):
>>> win32api.GetLongPathName(win32api.GetShortPathName(''stopservices.vbs''))
''StopServices.vbs''
Prefiero el enfoque de Ethan y xvorsx. AFAIK, lo siguiente tampoco dañaría 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]
Solo estaba luchando con el mismo problema. No estoy seguro, pero creo que las respuestas anteriores no cubren todos los casos. Mi problema real fue que la letra de la unidad de disco era diferente a la que ve el sistema. Aquí está mi solución que también comprueba la carcasa correcta de la letra de la unidad (utilizando 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://.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
Este hilo de python-win32 tiene una respuesta que no requiere paquetes de terceros o caminar por 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
os.walk
, pero creo que para 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
Ethan responde solo el nombre de archivo correcto, no los nombres de las subcarpetas en la ruta. Aquí está mi conjetura:
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]