代码之家  ›  专栏  ›  技术社区  ›  Ned Batchelder

在Python中,如何获得文件的正确大小写路径?

  •  21
  • Ned Batchelder  · 技术社区  · 14 年前

    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相同。

    有没有直接的方法?

    11 回复  |  直到 14 年前
        1
  •  7
  •   Ethan Furman    13 年前

    下面是一个简单的、仅限stdlib的解决方案:

    import glob
    def get_actual_filename(name):
        name = "%s[%s]" % (name[:-1], name[-1])
        return glob.glob(name)[0]
    
        2
  •  13
  •   Paul Moore    14 年前

    奈德的 GetLongPathName 这个答案不太管用(至少对我不管用)。你需要打电话 GetLongPath名称 论收益价值 GetShortPathname

    >>> win32api.GetLongPathName(win32api.GetShortPathName('stopservices.vbs'))
    'StopServices.vbs'
    
        3
  •  8
  •   Community CDub    8 年前

    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
  •  8
  •   kxr    8 年前

    这一方法统一、缩短并修正了几种方法:

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

    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
  •  7
  •   Ned Batchelder    14 年前

    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
    
        6
  •  5
  •   TheAl_T    6 年前

    pathlib resolve() :

    >>> from pathlib import Path
    
    >>> str(Path(r"C:\WiNdOwS\SyStEm32\DeSkToP.iNi").resolve())
    r'C:\Windows\System32\desktop.ini'
    
        7
  •  4
  •   msw    14 年前

    由于NTFS(或VFAT)文件系统上“true case”的定义真的很奇怪,所以最好的方法似乎是沿着这条路走并与之匹配 os.listdir() .

        8
  •  3
  •   Dobedani    10 年前

    我更喜欢伊桑和xvorsx的方法。好吧,在其他平台上,以下几点也不会造成伤害:

    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]
    
        9
  •  2
  •   Brendan Abel    9 年前

    基于上面的几个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
    
        10
  •  1
  •   Michał Niklas    14 年前

    我会用 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
    
        11
  •  1
  •   HoldOffHunger Lux    4 年前

      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 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
    
        12
  •  0
  •   Paul    5 年前

    我在寻找一个更简单的版本,即“glob技巧”,所以我做了这个,它只使用操作系统列表目录().

    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
    

    编辑:顺便说一句,效果不错。我不确定 None 当路径不存在时是预期的,但我需要这种行为。我想这可能会引起一个错误。