在Python中,如何获取文件的正确路径?

时间:2010-09-11 19:06:43

标签: python windows filenames

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没有的符号链接,因此它与Windows上的abspath相同。

有直接的方法吗?

12 个答案:

答案 0 :(得分:13)

Ned的GetLongPathName答案不太合适(至少不适合我)。您需要在GetLongPathName的返回值上调用GetShortPathname。使用pywin32简洁(ctypes解决方案看起来类似于Ned):

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

答案 1 :(得分:7)

This python-win32 thread的答案不需要第三方软件包或走树:

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

答案 2 :(得分:7)

这是一个简单的,仅限stdlib的解决方案:

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

答案 3 :(得分:7)

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]

答案 4 :(得分:6)

这个统一,缩短并修复了几种方法: 仅限标准库;转换所有路径部分(驱动器号除外);相对或绝对路径;驱动信函是否; 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

答案 5 :(得分:4)

由于NTFS(或VFAT)文件系统上的“真实案例”的定义确实很奇怪,因此最好的方法是走路并与os.listdir()匹配。

是的,这似乎是一个人为的解决方案,但NTFS路径也是如此。我没有DOS机器来测试它。

答案 6 :(得分:2)

我更喜欢Ethan和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]

答案 7 :(得分:2)

基于上面的几个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

答案 8 :(得分:1)

我会使用os.walk,但我认为对于包含许多目录的diskw来说,这可能很耗时:

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

答案 9 :(得分:1)

我正在努力解决同样的问题。我不确定,但我认为以前的答案并未涵盖所有案例。我的实际问题是驱动器字母外壳与系统看到的不同。这是我的解决方案,也检查正确的驱动器号套管(使用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

答案 10 :(得分:0)

在Python 3中,您可以使用pathlib的{​​{1}}:

resolve()

答案 11 :(得分:0)

我一直在寻找一个甚至更简单的版本(“技巧”),所以我做到了,它仅使用os.listdir()。

def casedPath(path):
    path = os.path.normpath(path).lower()
    parts = path.split(os.sep)
    result = parts[0].upper()
    # check that root actually exists
    if not os.path.exists(result):
        return
    for part in parts[1:]:
        actual = next((item for item in os.listdir(result) if item.lower() == part), None)
        if actual is None:
            # path doesn't exist
            return
        result += os.sep + actual
    return result

edit:顺便说一句,它工作正常。不确定在不存在路径时返回None的情况,但是我需要这种行为。我想这可能会引发错误。