Ответ 1
Здесь простое, только stdlib решение:
import glob
def get_actual_filename(name):
name = "%s[%s]" % (name[:-1], name[-1])
return glob.glob(name)[0]
Windows использует имена, не учитывающие регистр, поэтому я могу открыть один и тот же файл с любым из них:
r"c:\windows\system32\desktop.ini"
r"C:\WINdows\System32\DESKTOP.ini"
r"C:\WiNdOwS\SyStEm32\DeSkToP.iNi"
и т.д.. Учитывая любой из этих путей, как я могу найти истинный случай? Я хочу, чтобы все они производили:
r"C:\Windows\System32\desktop.ini"
os.path.normcase
не делает этого, он просто уменьшает все. os.path.abspath
возвращает абсолютный путь, но каждый из них уже является абсолютным, и поэтому он не меняет ни одного из них. os.path.realpath
используется только для разрешения символических ссылок, которые Windows не имеет, поэтому он аналогичен abspath в Windows.
Есть ли простой способ сделать это?
Здесь простое, только stdlib решение:
import glob
def get_actual_filename(name):
name = "%s[%s]" % (name[:-1], name[-1])
return glob.glob(name)[0]
Нед GetLongPathName
ответ не совсем работает (по крайней мере, не для меня). Вы должны вызвать GetLongPathName
для возвращаемого значения GetShortPathname
. Используя pywin32 для краткости (решение ctypes будет похоже на Ned's):
>>> win32api.GetLongPathName(win32api.GetShortPathName('stopservices.vbs'))
'StopServices.vbs'
Этот поток python-win32 имеет ответ, который не требует сторонних пакетов или идет по дереву:
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
Ethan answer исправить только имя файла, а не имена подпапок на пути. Вот моя догадка:
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]
Этот объединяет, сокращает и исправляет несколько подходов: Только стандартный lib; преобразует все части пути (кроме буквы диска); относительные или абсолютные пути; диск букв или нет; tolarant:
def casedpath(path):
r = glob.glob(re.sub(r'([^:/\\])(?=[/\\]|$)', r'[\1]', path))
return r and r[0] or path
И этот путь дополнительно обрабатывает UNC-пути:
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
Поскольку определение "истинного случая" в файловых системах NTFS (или VFAT) действительно странно, кажется, что лучший способ - пройти путь и сопоставить его с os.listdir().
Да, это похоже на надуманное решение, но также пути NTFS. У меня нет машины DOS, чтобы проверить это.
Я предпочитаю подход Этана и xvorsx. AFAIK, следующее не повредило бы и на других платформах:
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]
На основе нескольких приведенных выше примеров listdir/walk, но поддерживает пути 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
Я бы использовал os.walk
, но я думаю, что для диска со многими каталогами может потребоваться много времени:
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
Я просто боролся с той же проблемой. Я не уверен, но я думаю, что предыдущие ответы не охватывают все случаи. Моя фактическая проблема заключалась в том, что корпус буклета диска отличается от того, который видит система. Вот мое решение, которое также проверяет правильность оболочки диска (используя 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
В Python 3 вы можете использовать pathlib
resolve()
:
>>> from pathlib import Path
>>> str(Path(r"C:\WiNdOwS\SyStEm32\DeSkToP.iNi").resolve())
r'C:\Windows\System32\desktop.ini'