Подтвердить что ты не робот

В Python, как я могу получить правильно обведенный путь для файла?

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.

Есть ли простой способ сделать это?

4b9b3361

Ответ 1

Здесь простое, только stdlib решение:

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

Ответ 2

Нед GetLongPathName ответ не совсем работает (по крайней мере, не для меня). Вы должны вызвать GetLongPathName для возвращаемого значения GetShortPathname. Используя pywin32 для краткости (решение ctypes будет похоже на Ned's):

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

Ответ 3

Этот поток 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

Ответ 4

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]

Ответ 5

Этот объединяет, сокращает и исправляет несколько подходов: Только стандартный 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

Ответ 6

Поскольку определение "истинного случая" в файловых системах NTFS (или VFAT) действительно странно, кажется, что лучший способ - пройти путь и сопоставить его с os.listdir().

Да, это похоже на надуманное решение, но также пути NTFS. У меня нет машины DOS, чтобы проверить это.

Ответ 7

Я предпочитаю подход Этана и 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]

Ответ 8

На основе нескольких приведенных выше примеров 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

Ответ 9

Я бы использовал 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

Ответ 10

Я просто боролся с той же проблемой. Я не уверен, но я думаю, что предыдущие ответы не охватывают все случаи. Моя фактическая проблема заключалась в том, что корпус буклета диска отличается от того, который видит система. Вот мое решение, которое также проверяет правильность оболочки диска (используя 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

Ответ 11

В Python 3 вы можете использовать pathlib resolve():

>>> from pathlib import Path

>>> str(Path(r"C:\WiNdOwS\SyStEm32\DeSkToP.iNi").resolve())
r'C:\Windows\System32\desktop.ini'