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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3692261/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 15:14:20  来源:igfitidea点击:

In Python, how can I get the correctly-cased path for a file?

pythonwindowsfilenames

提问by Ned Batchelder

Windows uses case-insensitive file names, so I can open the same file with any of these:

Windows 使用不区分大小写的文件名,因此我可以使用以下任一文件打开同一个文件:

r"c:\windows\system32\desktop.ini"
r"C:\WINdows\System32\DESKTOP.ini"
r"C:\WiNdOwS\SyStEm32\DeSkToP.iNi"

etc. Given any of these paths, how can I find the true case? I want them all to produce:

等等。给定这些路径中的任何一条,我怎样才能找到真实的案例?我希望他们都能生产:

r"C:\Windows\System32\desktop.ini"

os.path.normcasedoesn't do it, it simply lowercases everything. os.path.abspathreturns an absolute path, but each of these is already absolute, and so it doesn't change any of them. os.path.realpathis only used to resolve symbolic links, which Windows doesn't have, so it's the same as abspath on Windows.

os.path.normcase不这样做,它只是将所有内容小写。os.path.abspath返回一个绝对路径,但这些路径中的每一个都已经是绝对路径,因此它不会改变它们中的任何一个。 os.path.realpath仅用于解析符号链接,Windows 没有,所以它与 Windows 上的 abspath 相同。

Is there a straightforward way to do this?

有没有直接的方法来做到这一点?

采纳答案by Ethan Furman

Here's a simple, stdlib only, solution:

这是一个简单的、仅限标准库的解决方案:

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

回答by Paul Moore

Ned's GetLongPathNameanswer doesn't quite work (at least not for me). You need to call GetLongPathNameon the return value of GetShortPathname. Using pywin32 for brevity (a ctypes solution would look similar to Ned's):

奈德的GetLongPathName回答不太有效(至少对我来说不是)。您需要调用GetLongPathName的返回值GetShortPathname。使用 pywin32 为简洁起见(ctypes 解决方案看起来类似于 Ned 的解决方案):

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

回答by xvorsx

Ethan answer correct only file name, not subfolders names on the path. Here is my guess:

Ethan回答正确的文件名,而不是路径上的子文件夹名称。这是我的猜测:

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]

回答by kxr

This one unifies, shortens and fixes several approaches: Standard lib only; converts all path parts (except drive letter); relative or absolute paths; drive letter'ed or not; tolarant:

这个统一、缩短和修复了几种方法: 仅标准库;转换所有路径部分(驱动器号除外);相对或绝对路径;驱动器盘符与否;容忍:

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

And this one handles UNC paths in addition:

而且这个还处理 UNC 路径:

def casedpath_unc(path):
    unc, p = os.path.splitunc(path)
    r = glob.glob(unc + re.sub(r'([^:/\])(?=[/\]|$)', r'[]', p))
    return r and r[0] or path

回答by Ned Batchelder

This python-win32 threadhas an answer that doesn't require third-party packages or walking the tree:

这个 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

回答by msw

Since the definition of "true case" on NTFS (or VFAT) filesystems is truly bizarre, it seems the best way would be to walk the path and match against os.listdir().

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

Yes, this seems like a contrived solution but so are NTFS paths. I don't have a DOS machine to test this on.

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

回答by TheAl_T

In Python 3 you can use the pathlib's resolve():

在 Python 3 中,您可以使用pathlib's resolve()

>>> from pathlib import Path

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

回答by Dobedani

I prefer the approach of Ethan and xvorsx. AFAIK, the following wouldn't also harm on other platforms:

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

回答by Brendan Abel

Based off a couple of the listdir/walk examples above, but supports UNC paths

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

回答by lutecki

I was just struggling with the same problem. I'm not sure, but I think the previous answers do not cover all cases. My actual problem was that the drive letter casing was different than the one seen by the system. Here is my solution that also checks for the correct drive letter casing (using win32api):

我只是在努力解决同样的问题。我不确定,但我认为之前的答案并未涵盖所有情况。我的实际问题是驱动器字母外壳与系统看到的不同。这是我的解决方案,它还检查正确的驱动器字母大小写(使用 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("##代码##0")[:-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