在使用HFS +的OSX上的python中,如何获得现有文件名的正确大小写?

Kei*_*ith 7 python filesystems macos hfs+

我正在存储有关OSX HFS +文件系统上存在的文件的数据.我后来想迭代存储的数据并确定每个文件是否仍然存在.出于我的目的,我关心文件名区分大小写,所以如果文件名的大小写已经改变,我会认为该文件不再存在.

我开始尝试

os.path.isfile(filename)
Run Code Online (Sandbox Code Playgroud)

但是在HFS +上正常安装OSX时,即使文件名大小写不匹配,也会返回True.我正在寻找一种方法来编写一个isfile()函数,即使文件系统没有,它也会关心大小写.

os.path.normcase()和os.path.realpath()都会在我传入文件的情况下返回文件名.

编辑:

我现在有两个函数似乎适用于限制为ASCII的文件名.我不知道unicode或其他角色会如何影响这个.

第一个是基于omz和Alex L.给出的答案.

def does_file_exist_case_sensitive1a(fname):
    if not os.path.isfile(fname): return False
    path, filename = os.path.split(fname)
    search_path = '.' if path == '' else path
    for name in os.listdir(search_path):
        if name == filename : return True
    return False
Run Code Online (Sandbox Code Playgroud)

第二个可能效率更低.

def does_file_exist_case_sensitive2(fname):
    if not os.path.isfile(fname): return False
    m = re.search('[a-zA-Z][^a-zA-Z]*\Z', fname)
    if m:
        test = string.replace(fname, fname[m.start()], '?', 1)
        print test
        actual = glob.glob(test)
        return len(actual) == 1 and actual[0] == fname
    else:
        return True  # no letters in file, case sensitivity doesn't matter
Run Code Online (Sandbox Code Playgroud)

以下是DSM的第三个答案.

def does_file_exist_case_sensitive3(fname):
    if not os.path.isfile(fname): return False
    path, filename = os.path.split(fname)
    search_path = '.' if path == '' else path
    inodes = {os.stat(x).st_ino: x for x in os.listdir(search_path)}
    return inodes[os.stat(fname).st_ino] == filename
Run Code Online (Sandbox Code Playgroud)

如果我在一个目录中有数千个文件,我不希望这些表现良好.我仍然希望能有更高效的东西.

我在测试这些时注意到的另一个缺点是它们只检查案例匹配的文件名.如果我传递一个包含目录名的路径,到目前为止这些函数都没有检查目录名的大小写.

mkl*_*nt0 6

此答案通过提供改编自Alex L 的答案的函数来补充现有答案,即:

  • 也适用于非 ASCII 字符
  • 处理所有路径组件(不仅仅是最后一个)
  • 使用 Python 2.x 和 3.x
  • 作为奖励,也适用于 Windows(有更好的Windows 特定解决方案 - 参见/sf/answers/148048281/ - 但这里的功能是跨平台的,不需要额外的包)
import os, unicodedata

def gettruecasepath(path): # IMPORTANT: <path> must be a Unicode string
  if not os.path.lexists(path): # use lexists to also find broken symlinks
    raise OSError(2, u'No such file or directory', path)
  isosx = sys.platform == u'darwin'
  if isosx: # convert to NFD for comparison with os.listdir() results
    path = unicodedata.normalize('NFD', path)
  parentpath, leaf = os.path.split(path)
  # find true case of leaf component
  if leaf not in [ u'.', u'..' ]: # skip . and .. components
    leaf_lower = leaf.lower() # if you use Py3.3+: change .lower() to .casefold()
    found = False
    for leaf in os.listdir(u'.' if parentpath == u'' else parentpath):
      if leaf_lower == leaf.lower(): # see .casefold() comment above
          found = True
          if isosx:
            leaf = unicodedata.normalize('NFC', leaf) # convert to NFC for return value
          break
    if not found:
      # should only happen if the path was just deleted
      raise OSError(2, u'Unexpectedly not found in ' + parentpath, leaf_lower)
  # recurse on parent path
  if parentpath not in [ u'', u'.', u'..', u'/', u'\\' ] and \
                not (sys.platform == u'win32' and 
                     os.path.splitdrive(parentpath)[1] in [ u'\\', u'/' ]):
      parentpath = gettruecasepath(parentpath) # recurse
  return os.path.join(parentpath, leaf)


def istruecasepath(path): # IMPORTANT: <path> must be a Unicode string
  return gettruecasepath(path) == unicodedata.normalize('NFC', path)
Run Code Online (Sandbox Code Playgroud)
  • gettruecasepath() 获取存储在指定路径(绝对或相对)路径的文件系统中的大小写精确表示(如果存在):

    • 输入路径必须Unicode字符串:
      • Python 3.x:字符串本身就是 Unicode - 不需要额外的操作。
      • Python 2.x:文字:前缀为u;例如u'Motörhead'; str 变量:转换为,例如,strVar.decode('utf8')
    • 返回的字符串是 NFC 中的 Unicode 字符串(复合标准形式)。即使在 OSX 上也返回 NFC,其中文件系统 (HFS+) 将名称存储在 NFD(分解的正常形式)中。
      返回 NFC,因为它比 NFD 更常见,并且 Python 不会将等效的 NFC 和 NFD 字符串识别为(概念上)相同。有关背景信息,请参见下文。
    • 返回的路径保留了输入路径的结构(相对与绝对,组件,例如...),除了折叠多个路径分隔符,并且在 Windows 上,返回的路径始终\用作路径分隔符。
    • 在 Windows 上,驱动器/UNC 共享组件(如果存在)按原样保留。
    • OSError,如果路径不存在,如果你没有权限访问它抛出异常,或。
    • 如果您在区分大小写的文件系统上使用此函数,例如在带有 ext4 的 Linux 上,它实际上会降级为指示输入路径是否存在于指定的确切大小写中。
  • istruecasepath()用于gettruecasepath()将输入路径与存储在文件系统中的路径进行比较。

警告:由于这些函数需要检查输入路径(如指定的)每个级别的所有目录条目,它们会很- 不可预测,因为性能将对应于检查的目录包含的项目数量。请继续阅读背景信息。


背景

本机 API 支持(缺乏)

奇怪的是 OSX 和 Windows 都没有提供直接解决这个问题的原生 API 方法。

虽然在 Windows 上你可以巧妙地结合两种 API 方法来解决这个问题,但在 OSX 上,除了上面所采用的在检查的路径的每个级别上缓慢枚举目录内容之外,我所知道的没有替代方法。

Unicode 范式:NFC 与 NFD

HFS+(OSX 文件系统)以分解的Unicode 形式 (NFD ) 存储文件名,这在将此类名称与大多数编程语言中的内存中 Unicode 字符串进行比较时会导致问题,这些字符串通常采用组合Unicode 形式 (NFC)。

例如,在源代码中ü指定为文字的具有非 ASCII 字符的路径将表示为单个Unicode 代码点,; 这是一个例子NFC:在“C”表示组成,这是因为信基本字母和其变音符号(一个组合分音符号)形成单个字母。U+00FCu¨

相比之下,如果您将其ü用作 HFS+文件名的一部分,它将被转换为NFD形式,从而产生2 个Unicode 代码点:基本字母u( U+0075),后跟组合分音符 ( ?, U+0308) 作为单独的代码点;'D' 代表分解,因为字符被分解为基本字母及其相关的变音符号。

尽管 Unicode 标准认为这两种表示(规范地)等效,但大多数编程语言,包括 Python,承认这种等效性。
在 Python 的情况下,您必须unicodedata.normalize()在比较之前使用将两个字符串转换为相同的形式。

(旁注:Unicode标准形式与 Unicode编码是分开的,尽管 Unicode 代码点的不同数量通常也会影响对每种形式进行编码所需的字节数。在上面的示例中,单代码点ü(NFC) 需要2 个字节来以 UTF-8 ( U+00FC-> 0xC3 0xBC) 编码,而双码点ü(NFD) 需要3 个字节U+0075->0x75U+0308-> 0xCC 0x88))。


Ale*_*x L 5

继 omz 的帖子之后 - 像这样的事情可能会奏效:

import os

def getcase(filepath):
    path, filename = os.path.split(filepath)
    for fname in os.listdir(path):
        if filename.lower() == fname.lower():
            return os.path.join(path, fname)

print getcase('/usr/myfile.txt')
Run Code Online (Sandbox Code Playgroud)


omz*_*omz 2

您可以使用类似的方法os.listdir检查列表是否包含您要查找的文件名。