跨平台隐性文件检测

25 投票
6 回答
26276 浏览
提问于 2025-04-11 18:24

处理隐藏文件的跨平台方法有什么好的建议吗?(最好是用Python,但其他方案也欢迎)

简单地检查文件名开头是否有一个点(.)在*nix和Mac系统上是有效的,而在Windows上则可以通过文件属性来判断。不过,这种方法似乎有点简单,也没有考虑到其他隐藏文件的方法(比如.hidden文件等等)。有没有什么标准的处理方式呢?

6 个回答

11

Jason R. Coombs的回答对于Windows来说已经足够了。而大多数POSIX系统的图形界面文件管理器或打开对话框等,可能也遵循“以点开头的文件是隐藏的”这个规则,就像ls命令一样。但Mac OS X就不一样了。

在Finder、文件打开面板等地方,文件或文件夹可以通过至少四种方式被隐藏:

  • 以点开头。
  • HFS+的不可见属性。
  • Finder信息中的隐藏标志。
  • 符合CoreFoundation内置的特殊黑名单(这个黑名单在每个操作系统版本上都不同,比如~/Library在10.7及以上版本是隐藏的,但在10.6中是可见的)。

自己写代码来处理这些隐藏文件可不是件简单的事。而且你还得不断更新,因为我敢打赌,黑名单会随着大多数操作系统版本的更新而变化,Finder信息最终会从被弃用变成完全不支持,扩展属性可能会比HFS+得到更广泛的支持……

不过,如果你可以使用pyobjc(这个在最近的Apple提供的Python中已经包含,其他情况下可以通过pip安装),你就可以直接调用Apple的代码:

import Foundation

def is_hidden(path):
    url = Foundation.NSURL.fileURLWithPath_(path)
    return url.getResourceValue_forKey_error_(None, Foundation.NSURLIsHiddenKey, None)[0]

def listdir_skipping_hidden(path):
    url = Foundation.NSURL.fileURLWithPath_(path)
    fm = Foundation.NSFileManager.defaultManager()
    urls = fm.contentsOfDirectoryAtURL_includingPropertiesForKeys_options_error_(
        url, [], Foundation.NSDirectoryEnumerationSkipsHiddenFiles, None)[0]
    return [u.path() for u in urls]

这段代码应该能在任何支持pyobjc的Python上运行,适用于OS X 10.6及以上版本。如果你需要10.5或更早的版本,目录枚举标志还不存在,所以唯一的选择就是像过滤contentsOfDirectoryAtPath_error_(或者直接使用os.listdir)来检查is_hidden

如果你必须在没有pyobjc的情况下工作,可以使用CoreFoundation的等效功能,并使用ctypes。关键函数是CFURLCopyResourcePropertyForKey用于is_hiddenCFURLEnumeratorCreateForDirectoryURL用于列出目录。

你可以查看http://pastebin.com/aCUwTumB获取实现代码。

我已经在以下环境中测试过:

  • OS X 10.6,32位python.org 3.3.0
  • OS X 10.8,32位Apple 2.7.2
  • OS X 10.8,64位Apple 2.7.2
  • OS X 10.8,64位python.org 3.3.0

在每个环境中都能正常工作(例如,在10.8中会跳过~/Library,但在10.6中会显示出来)。

这段代码应该能在任何OS X 10.6及以上版本和任何Python 2.6及以上版本上运行。如果你需要OS X 10.5,就需要使用旧的API(或者os.listdir)并过滤is_hidden。如果你需要Python 2.5,就需要把bytes的检查改成str的检查(这会导致3.x版本不兼容),并把with改成丑陋的try/finally或手动释放。

如果有人打算把这段代码放到一个库里,我强烈建议先检查一下pyobjc是否可用(import Foundation,如果没有报ImportError就说明成功了),只有在不可用的情况下才使用ctypes的代码。


最后一点:

一些寻找这个答案的人其实是在重新发明一个不需要的轮子。

通常,当人们在做这样的事情时,他们是在构建一个图形界面,想要展示一个文件浏览器,并希望有一个选项来隐藏或显示隐藏文件。许多流行的跨平台图形界面框架(如Qt、wx等)都内置了这个支持。(而且,它们中的许多都是开源的,你可以查看它们的代码,看看它们是如何实现的。)

这可能并不能直接回答你的问题——例如,它们可能只是向平台的原生文件浏览器对话框传递了一个“过滤隐藏文件”的标志,但你想构建一个控制台模式的文件浏览器,无法做到这一点。不过,如果它能满足你的需求,那就直接使用吧。

23

这里有一个可以在 Python 2.5 及以上版本运行的脚本,应该能满足你的需求:

import ctypes
import os

def is_hidden(filepath):
    name = os.path.basename(os.path.abspath(filepath))
    return name.startswith('.') or has_hidden_attribute(filepath)

def has_hidden_attribute(filepath):
    try:
        attrs = ctypes.windll.kernel32.GetFileAttributesW(unicode(filepath))
        assert attrs != -1
        result = bool(attrs & 2)
    except (AttributeError, AssertionError):
        result = False
    return result

我在 jaraco.windows 中添加了类似 has_hidden_attribute 的功能。如果你使用的是 jaraco.windows 版本大于等于 2.3:

from jaraco.windows import filesystem

def has_hidden_attribute(filepath):
    return filesystem.GetFileAttributes(filepath).hidden

正如 Ben 指出的,在 Python 3.5 中,你可以使用标准库(stdlib):

import os, stat

def has_hidden_attribute(filepath):
    return bool(os.stat(filepath).st_file_attributes & stat.FILE_ATTRIBUTE_HIDDEN)

不过你可能还是想用 jaraco.windows,因为它提供了更符合 Python 风格的接口。

2

我们在一个项目中其实解决了这个问题。我们做的事情是有几个不同的“隐藏文件检查器”,这些检查器都注册在一个主检查器下面。我们会把每个文件都通过这些检查器,看看它是否应该被隐藏。

这些检查器不仅适用于不同的操作系统,还可以处理版本控制中被“忽略”的文件,以及用户通过通配符或正则表达式设置的可选覆盖。

这基本上就是你所做的事情,但我们采用了一种可插拔、灵活和可扩展的方式。

源代码可以在这里查看:https://bitbucket.org/aafshar/pida-main/src/tip/pida/services/filemanager/filemanager.py

撰写回答