This Swift script allows users to search for files of a specific type in a chosen directory on macOS. It interactively prompts for a directory (from common options or a custom path) and a file extension, then recursively locates matching files and displays their names along with human-readable file sizes.
Click to view script…
#!/usr/bin/env swift
import Foundation
// MARK: - Helper: Format File Size in Human-Readable Format
func humanReadableSize(_ size: UInt64) -> String {
let formatter = ByteCountFormatter()
formatter.countStyle = .file
// Allow units from bytes up to TB
formatter.allowedUnits = [.useKB, .useMB, .useGB, .useTB]
return formatter.string(fromByteCount: Int64(size))
}
// MARK: - Helper: Prompt for User Input
func prompt(_ message: String) -> String {
print(message, terminator: " ")
return readLine() ?? ""
}
// MARK: - Prepare Candidate Directories
let fileManager = FileManager.default
var candidateDirectories: [String: URL] = [:]
if let desktopURL = fileManager.urls(for: .desktopDirectory, in: .userDomainMask).first {
candidateDirectories["Desktop"] = desktopURL
}
if let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first {
candidateDirectories["Documents"] = documentsURL
}
if let downloadsURL = fileManager.urls(for: .downloadsDirectory, in: .userDomainMask).first {
candidateDirectories["Downloads"] = downloadsURL
}
// Provide an option for a custom directory.
candidateDirectories["Other"] = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
// MARK: - Display Directory Options
print("Select a directory to search in:")
let directoryKeys = Array(candidateDirectories.keys).sorted() // sorted for consistent ordering
for (index, key) in directoryKeys.enumerated() {
if key == "Other" {
print("\(index + 1): Enter a custom directory path")
} else if let url = candidateDirectories[key] {
print("\(index + 1): \(key) -> \(url.path)")
}
}
guard let dirChoiceStr = readLine(), let choiceNum = Int(dirChoiceStr),
choiceNum > 0, choiceNum <= directoryKeys.count else {
print("Invalid directory choice. Exiting.")
exit(1)
}
let chosenKey = directoryKeys[choiceNum - 1]
var searchDirectory: URL
if chosenKey == "Other" {
let customPath = prompt("Enter full directory path:")
searchDirectory = URL(fileURLWithPath: customPath, isDirectory: true)
} else {
searchDirectory = candidateDirectories[chosenKey]!
}
// Verify the directory exists
var isDir: ObjCBool = false
guard fileManager.fileExists(atPath: searchDirectory.path, isDirectory: &isDir), isDir.boolValue else {
print("The selected path does not exist or is not a directory. Exiting.")
exit(1)
}
// MARK: - Get File Extension to Search For
let fileExtensionInput = prompt("Enter file extension to search for (e.g., txt, pdf, jpg):")
let fileExtension = fileExtensionInput.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
if fileExtension.isEmpty {
print("No file extension provided. Exiting.")
exit(1)
}
// Validate file extension (alphanumeric only)
let regex = try! NSRegularExpression(pattern: "^[a-zA-Z0-9]+$")
let range = NSRange(location: 0, length: fileExtension.utf16.count)
if regex.firstMatch(in: fileExtension, options: [], range: range) == nil {
print("Invalid file extension provided. Exiting.")
exit(1)
}
// MARK: - Search for Files
print("\nSearching for .\(fileExtension) files in \(searchDirectory.path)...")
guard let enumerator = fileManager.enumerator(at: searchDirectory,
includingPropertiesForKeys: [.fileSizeKey],
options: [.skipsHiddenFiles],
errorHandler: { (url, error) -> Bool in
print("Error accessing \(url.path): \(error.localizedDescription)")
return true
}) else {
print("Failed to create directory enumerator. Exiting.")
exit(1)
}
var foundFiles: [(url: URL, size: UInt64)] = []
while let fileURL = enumerator.nextObject() as? URL {
if fileURL.pathExtension.lowercased() == fileExtension {
do {
let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey])
if let fileSize = resourceValues.fileSize {
foundFiles.append((fileURL, UInt64(fileSize)))
}
} catch {
print("Error getting size for \(fileURL.path): \(error.localizedDescription)")
}
}
}
// MARK: - Display Results
if foundFiles.isEmpty {
print("No files with .\(fileExtension) extension were found in \(searchDirectory.path).")
} else {
print("\nFound \(foundFiles.count) file(s):")
for (file, size) in foundFiles {
print("\(file.lastPathComponent) - \(humanReadableSize(size))")
}
}