Group Group Group Group Group Group Group Group Group

Directory Enumeration & Finding Files by Filename

Coming from the FileManager Class Tutorial (FileManager Class Tutorial for macOS: Getting Started with the File System | Ray Wenderlich), I’ve been trying to produce a function that searches a user-chosen directory for files that contain a keyword(s).

This seems possible if you’re searching by path extension (Opening a Finder window) or by using hasPrefix? I currently have:

let scanItems = try! fileManager.contentsOfDirectory(atPath: chooseLocationPath.stringValue)

    for scanItem in scanItems {
        if scanItem.hasPrefix("Policy") {
            print(scanItem)
        }
    }

But I want to search by multiple keywords at once ex. “Policy”, “policy”, “Money”, “money” and so on – meaning, return files that contain one-or-more of these keywords in them (similar to Automator’s ‘Find Finder Items’ action I suppose). I also think I need to use a directory enumerator here to search deeper – but would like to know if my goal is achievable and if anybody could point me in the right direction!

Edit: Using something like this could work, but would be really onerous and messy if you had to repeat parts of it for every keyword you were interested in. Hmm still stumped.

    let enumerator = FileManager.default.enumerator(atPath: path!)
        let filePaths = enumerator?.allObjects as! [String]
        let scanItems = filePaths.filter{ $0.contains("policy") } // and so forth with other keywords
        for scanItem in scanItems{
            print(scanItem)
        }

What if you stored the keywords in an array and then looped over that array in the filter?
Here is an example where I replaced your filter to do this:

let keywords = ["policy", "money", "finance"]

let enumerator = fileManager.enumerator(atPath: url.path)
let filePaths = enumerator?.allObjects as! [String]
let scanItems = filePaths.filter { fileName in
    let fileNameLower = fileName.lowercased()
    for keyword in keywords {
        if fileNameLower.contains(keyword) {
            return true
        }
    }
    return false
}

for scanItem in scanItems {
    print(scanItem)
}

I change the file name to lower case before testing so I only have to do a single test for “POLICY”, “Policy”, “policy” or any other variant. This assumes the keywords are already in lower case, but if they are entered by the user, you will need to change them all to lower case, before starting this process.

Thanks Sarah! This got me exploring arrays/sets and led me to connect an NSSegmentControl that changes the array’s keywords. Awesome suggestion!! :raised_hands: Now to go learn about lowercased

1 Like

Hi @sarah,
You can simplify the code further and rather than iterate through the keywords array, you can save those cycles by simple using the code below.

let scanItems = filePaths.filter { fileName in 
let fileNameLower = fileName.lowercased()
return keywords.contains(fileNameLower) 
}

cheers,

I think this solution will only work if you are looking for exact file names. If keywords is [“policy”, “money”] and you want to find “policy_statement.doc”, “holiday money.numbers” and so on, I think you will have to do it the longer way.

yes @sarah,
You are correct on the limitation of this option.

Have you considered a where clause in contains, looking at the range which will return if a substring is contained as an array element.

let result = keywords.contains(where: {
    $0.range(of: fileName, option: .caseInsensitive) != nil
})

This way you do not have to change the case before testing

cheers,

@lewis-h,
dir is available on DOS/Windows (Microsoft platform) on the *nix systems is it ls
Secondly, the original question was enumerating the files programatically not manually and the files list1.txt is not automatically created when you use the dir or ls command.

There is a bit of a disconnect between the response you are referring to and the question.