使用 Swift 的 FileManager 遍历文件夹及其子文件夹中的文件

Posted

技术标签:

【中文标题】使用 Swift 的 FileManager 遍历文件夹及其子文件夹中的文件【英文标题】:Iterate through files in a folder and its subfolders using Swift's FileManager 【发布时间】:2014-08-13 11:31:31 【问题描述】:

我对 Swift 编程很陌生,我正在尝试遍历文件夹中的文件。 我看了一下答案here 并尝试将其翻译成 Swift 语法,但没有成功。

let fileManager = NSFileManager.defaultManager()
let enumerator:NSDirectoryEnumerator = fileManager.enumeratorAtPath(folderPath)

for element in enumerator 
    //do something

我得到的错误是:

Type 'NSDirectoryEnumerator' does not conform to protocol 'SequenceType'

我的目标是查看包含在主文件夹中的所有子文件夹和文件,并找到所有具有特定扩展名的文件,然后对它们进行处理。

【问题讨论】:

【参考方案1】:

使用enumeratornextObject()方法:

while let element = enumerator?.nextObject() as? String 
    if element.hasSuffix("ext")  // checks the extension
    

【讨论】:

根据下面 user1398498 的回答,如果没有可选链接,则无法使其正常工作。 刚刚尝试过这个,除了无限循环,我什么也没有,只好粗略地采用雷尼尔的方法。 很奇怪,一个相当粗糙的while 比密集的for 解决方案更受欢迎。 (哦,我看到我已经对其他解决方案进行了类似的评论 - 无论如何) 对于那些使用enumerator(at:, includingPropertiesForKeys:, options:) 的人来说,考虑到该元素实际上是一个 URL 而不是字符串,所以如果您使用该代码,它将不会返回任何内容。 while let element = enumerator?.nextObject() as? URL ... 很好的参考。我也从下面的链接中找到了它。 developer.apple.com/documentation/foundation/filemanager/…【参考方案2】:

现在(2017 年初)强烈建议使用更通用的 URL 相关 API

let fileManager = FileManager.default

do 
    let resourceKeys : [URLResourceKey] = [.creationDateKey, .isDirectoryKey]
    let documentsURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
    let enumerator = FileManager.default.enumerator(at: documentsURL,
                            includingPropertiesForKeys: resourceKeys,
                                               options: [.skipsHiddenFiles], errorHandler:  (url, error) -> Bool in
                                                        print("directoryEnumerator error at \(url): ", error)
                                                        return true
    )!

    for case let fileURL as URL in enumerator 
        let resourceValues = try fileURL.resourceValues(forKeys: Set(resourceKeys))
        print(fileURL.path, resourceValues.creationDate!, resourceValues.isDirectory!)
    
 catch 
    print(error)

【讨论】:

感谢您的周到。 (对于路径类型的版本,我一点运气都没有,因为 myURL.absolutePath 对于 NSFileEnumerator 而言不是有效的基于字符串的路径,并且不会创建有效的枚举器)。 URLResourceKeys的列表,看developer.apple.com/documentation/foundation/urlresourcekey 这是一个很好的例子,说明了如何执行深度搜索,对我来说效果很好。感谢您的精彩贡献。 这段代码很有帮助。但是我想至少在根文件夹中的第一个子级目录中获得遍历文件的进度(当然,我知道在我们枚举所有文件之前,我们无法获得确切的文件总数。因此,它会很好了解“至少第一个子级别”的获取进度。)。有什么办法可以取得进展吗?【参考方案3】:

我根本无法使用 pNre 的解决方案; while 循环从来没有收到任何东西。但是,我确实遇到了这个对我有用的解决方案(在 Xcode 6 beta 6 中,所以自从 pNre 发布上述答案后情况可能发生了变化?):

for url in enumerator!.allObjects 
    print("\((url as! NSURL).path!)")

【讨论】:

有趣的是,这个解决方案的赞成票很少,因为它的语法比当前最喜欢的要简单得多。 它之所以有投票权是因为它依赖于强制解包枚举器(枚举器!)而不是枚举器?在投票最多的答案中。 当心这是无法使用的大目录 - 它可能会挂起几分钟。 停止强制展开。【参考方案4】:

Swift3 + 绝对网址

extension FileManager 
    func listFiles(path: String) -> [URL] 
        let baseurl: URL = URL(fileURLWithPath: path)
        var urls = [URL]()
        enumerator(atPath: path)?.forEach( (e) in
            guard let s = e as? String else  return 
            let relativeURL = URL(fileURLWithPath: s, relativeTo: baseurl)
            let url = relativeURL.absoluteURL
            urls.append(url)
        )
        return urls
    

基于来自@user3441734的代码

【讨论】:

【参考方案5】:

返回目录+子目录中的所有文件

import Foundation

let path = "<some path>"

let enumerator = FileManager.default.enumerator(atPath: path)

while let filename = enumerator?.nextObject() as? String 
        print(filename)

【讨论】:

它不会进入子目录,也不会获取隐藏文件,也不会跟随符号链接 我们如何获取完整路径而不是文件名? 你使用什么样的路径?就我而言,我在 xcode 项目中有“mydir”目录。当我使用let path = "mydir" 时,什么都没有发生。我正在使用 swift 5。 这是一个完整的路径。有没有办法使用相对路径,以便它可以包含在项目文件夹中?【参考方案6】:

我以前回答的两分钱.. 更快捷,有可选:

 let enumerator = FileManager.default.enumerator(atPath: folderPath)
    while let element = enumerator?.nextObject() as? String 
        print(element)

        if let fType = enumerator?.fileAttributes?[FileAttributeKey.type] as? FileAttributeType

            switch fType
            case .typeRegular:
                print("a file")
            case .typeDirectory:
                print("a dir")
            
        

    

【讨论】:

这是这里最有用的条目,因为它突出显示了按目录和文件类型挑选条目。这似乎是此类功能中被遗忘的部分。【参考方案7】:

斯威夫特 3

let fd = FileManager.default
fd.enumerator(atPath: "/Library/FileSystems")?.forEach( (e) in
    if let e = e as? String, let url = URL(string: e) 
        print(url.pathExtension)
    
)

【讨论】:

当我使用这段代码时,我发现路径中带有空格的目录/文件被忽略了。知道为什么会这样吗?然后我尝试了@vadian 的代码,它按预期工作。【参考方案8】:

如果你得到了

'NSDirectoryEnumerator?'没有名为'nextObject'的成员错误

while 循环应该是:

while let element = enumerator?.nextObject() as? String 
  // do things with element

和optional chaining有关

【讨论】:

【参考方案9】:

SWIFT 3.0

返回传递的目录及其子目录中的所有扩展名文件

func extractAllFile(atPath path: String, withExtension fileExtension:String) -> [String] 
    let pathURL = NSURL(fileURLWithPath: path, isDirectory: true)
    var allFiles: [String] = []
    let fileManager = FileManager.default
    let pathString = path.replacingOccurrences(of: "file:", with: "")
    if let enumerator = fileManager.enumerator(atPath: pathString) 
        for file in enumerator 
            if #available(ios 9.0, *) 
                if let path = NSURL(fileURLWithPath: file as! String, relativeTo: pathURL as URL).path, path.hasSuffix(".\(fileExtension)")
                    let fileNameArray = (path as NSString).lastPathComponent.components(separatedBy: ".")
                    allFiles.append(fileNameArray.first!)
                
             else 
                // Fallback on earlier versions
                print("Not available, #available iOS 9.0 & above")
            
        
    
    return allFiles

【讨论】:

【参考方案10】:

为 Swift 3 更新:

let fileManager = FileManager()     // let fileManager = NSFileManager.defaultManager()
let en=fileManager.enumerator(atPath: the_path)   // let enumerator:NSDirectoryEnumerator = fileManager.enumeratorAtPath(folderPath)

while let element = en?.nextObject() as? String 
    if element.hasSuffix("ext") 
        // do something with the_path/*.ext ....
    

【讨论】:

【参考方案11】:

补充到 vadian 的回复——Apple 文档提到基于路径的 URL 在某些方面更简单,但文件引用 URL 的优点是,如果在您的应用程序运行时移动或重命名文件,引用仍然有效。

来自“访问文件和目录”的文档:

“基于路径的 URL 更容易操作、更容易调试,并且通常是 NSFileManager 等类的首选。文件引用 URL 的一个优点是,在您的应用程序运行时,它们比基于路径的 URL 更不脆弱。如果用户在 Finder 中移动文件,任何引用该文件的基于路径的 URL 都会立即失效,必须更新到新路径。但是,只要文件移动到同一个磁盘上的另一个位置,它的唯一ID 不会改变,任何文件参考 URL 仍然有效。”

https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/AccessingFilesandDirectories/AccessingFilesandDirectories.html

【讨论】:

【参考方案12】:

如果要分类检查一个元素是文件还是子目录:

let enumerator = FileManager.default.enumerator(atPath: contentsPath);
while let element = enumerator?.nextObject() as? String              
   if(enumerator?.fileAttributes?[FileAttributeKey.type] as! FileAttributeType == FileAttributeType.typeRegular)
                //this is a file
   
   else if(enumerator?.fileAttributes?[FileAttributeKey.type] as! FileAttributeType == FileAttributeType.typeDirectory) 
                //this is a sub-directory
    

【讨论】:

【参考方案13】:

最近在处理一组 url 时遇到了这个问题,无论它们是否是目录(例如拖放)。在 swift 4 中结束了这个扩展,可能有用

extension Sequence where Iterator.Element == URL 

    var handleDir: [URL] 
        var files: [URL] = []
        self.forEach  u in
            guard u.hasDirectoryPath else  return files.append(u.resolvingSymlinksInPath()) 
            guard let dir = FileManager.default.enumerator(at: u.resolvingSymlinksInPath(), includingPropertiesForKeys: nil) else  return 
            for case let url as URL in dir 
                files.append(url.resolvingSymlinksInPath())
            
        
        return files
    

【讨论】:

【参考方案14】:

避免使用引用 URL,虽然它们确实具有如上所述的一些优势,但它们会占用系统资源,如果您要枚举大型文件系统(实际上并没有那么大),您的应用程序将很快撞到系统墙并被 macOS 关闭。

【讨论】:

不回答这个问题,这应该更像是一个评论而不是一个答案。

以上是关于使用 Swift 的 FileManager 遍历文件夹及其子文件夹中的文件的主要内容,如果未能解决你的问题,请参考以下文章

如何在 swift 中使用 fileManager.unmountVolume

如何使用 Swift 处理错误(FileManager 和其他一般)[关闭]

swift 使用FileManager保存/检索数据

FileManager MoveItem 回调 - Swift

Swift 3 FileManager.default(......).first 是啥意思?

FileManager.default.contentsOfDirectory 在 swift 3 中失败