在 Python 中使用 glob.glob 和带有 unicode 文件名的正则表达式的文件系统独立方式

Filesystem independent way of using glob.glob and regular expressions with unicode filenames in Python

我正在开发一个我想保持平台、文件系统和 Python2.x/3.x 独立的库。但是,我不知道如何以 platform/file-system 独立的方式对文件进行 glob 匹配,并将文件名与正则表达式进行匹配。

例如(在 Mac,使用 IPython,Python 2.7):

   In[7]: from glob import glob
   In[8]: !touch 'ü-0.é' # Create the file in the current folder

   In[9]: glob(u'ü-*.é')
  Out[9]: []

   In[10]: import unicodedata as U

   In[11]: glob(U.normalize('NFD', u'ü-*.é'))
  Out[11]: [u'u\u0308-0.e\u0301']

但是,这不适用于 Linux 或 Windows,我需要 unicode.normalize('NFC', u'ü-*.é')。当我尝试将文件名与正则表达式进行匹配时,会出现同样的问题:只有在 Mac 上标准化为 NFD 的 unicode 正则表达式匹配文件名,而只有 NFC 正则表达式匹配读取的文件名在 Linux/Windows 上(我在这两种情况下都使用 re.UNICODE 标志)。

是否有处理此问题的标准方法?

我希望就像 sys.getfilesystemencoding() returns 文件系统的编码一样,存在一个 returns 底层文件系统使用的 Unicode 规范化的函数。

但是,我既找不到这样的函数,也找不到 safe/standard 对其进行功能测试的方法。


Mac + HFS+ 使用 NFD 规范化:https://apple.stackexchange.com/a/10484

Linux + Windows 使用 NFC 标准化:http://qerub.se/filenames-and-unicode-normalization-forms

Link 编码:https://github.com/musically-ut/seqfile/blob/feat-unicode/seqfile/seqfile.py

我是这样解决问题的:

import unicodedata as U

    # ...

    globPattern = os.path.join(folder, prefix + u'*' + suffix)
    rawRegEx = prefix + u'([0-9]+)' + suffix + u'$'

    # Mac uses NFD normalization for Unicode filenames while windows
    # linux/windows use NFC normalization
    if sys.platform.startswith('darwin'):
        normalizedGlobPattern = U.normalize('NFD', globPattern)
        normalizedRegEx = U.normalize('NFD', rawRegEx)
    else:
        normalizedGlobPattern = U.normalize('NFC', globPattern)
        normalizedRegEx = U.normalize('NFC', rawRegEx)

    allFiles = glob.glob(normalizedGlobPattern)

    # ...

    numFilesRegEx = re.compile(normalizedRegEx, _re.UNICODE)
    numberedFiles = (re.search(numFilesRegEx, f) for f in allFiles
                     if re.search(numFilesRegEx, f))

这似乎通过了我可以在 AppVeyor (Windows)、Travis (Linux) 和我的笔记本电脑 (Mac + HFS+) 上进行的所有测试.

但是,我不确定这是否安全是否有更好的写法。例如,我不知道它是否可以在安装了 NFS 的 Mac 上运行。

我假设您想匹配 unicode equivalent 文件名,例如您希望 u'\xE9*' 的输入模式匹配任何操作系统上的文件名 u'\xE9qui'u'e\u0301qui',即字符级模式匹配。

你必须明白这不是 Linux 上的默认设置,其中字节被视为字节,并且在当前系统编码中并非每个文件名都是有效的 unicode 字符串(尽管 Python 3 使用 'surrogateescape' 错误处理程序将它们表示为 str 无论如何)。

考虑到这一点,这是我的解决方案:

def myglob(pattern, directory=u'.'):
    pattern = unicodedata.normalize('NFC', pattern)
    results = []
    enc = sys.getfilesystemencoding()
    for name in os.listdir(directory):
        if isinstance(name, bytes):
            try:
                name = name.decode(enc)
            except UnicodeDecodeError:
                # Filenames that are not proper unicode won't match any pattern
                continue
        if fnmatch.filter([unicodedata.normalize('NFC', name)], pattern):
            results.append(name)
    return results