macOS Swift 文件操作示例
macOS Swift 文件操作示例,包括文本文件读写、文件复制移动、目录遍历和文件验证
💻 文本文件读写 swift
🟢 simple
⭐⭐
使用各种编码选项和错误处理读取和写入文本文件
⏱️ 20 min
🏷️ swift, macos, file operations, io
Prerequisites:
Basic Swift knowledge, Foundation framework
// macOS Swift Text File Read/Write Examples
// Using Foundation framework
import Foundation
// 1. Basic Text File Writing
class TextFileWriter {
// Write simple text to file
static func writeText(to path: String, text: String) throws {
try text.write(toFile: path, atomically: true, encoding: .utf8)
print("Text written to: \(path)")
}
// Write text with specific encoding
static func writeText(to path: String, text: String, encoding: String.Encoding) throws {
try text.write(toFile: path, atomically: true, encoding: encoding)
print("Text written with \(encoding) to: \(path)")
}
// Append text to existing file
static func appendText(to path: String, text: String) throws {
let fileHandle = FileHandle(forWritingAtPath: path)
if let handle = fileHandle {
handle.seekToEndOfFile()
if let data = text.data(using: .utf8) {
handle.write(data)
}
handle.closeFile()
print("Text appended to: \(path)")
} else {
// File doesn't exist, create new
try writeText(to: path, text: text)
}
}
// Write text with line endings
static func writeLines(to path: String, lines: [String]) throws {
let content = lines.joined(separator: "\n")
try writeText(to: path, text: content)
print("\(lines.count) lines written to: \(path)")
}
// Write with FileHandle (more control)
static func writeWithFileHandle(to path: String, text: String) throws {
guard let data = text.data(using: .utf8) else {
throw NSError(domain: "TextError", code: 1, userInfo: [
NSLocalizedDescriptionKey: "Failed to convert text to data"
])
}
FileManager.default.createFile(atPath: path, contents: data, attributes: nil)
print("File created at: \(path)")
}
}
// 2. Basic Text File Reading
class TextFileReader {
// Read entire file as string
static func readText(from path: String) throws -> String {
let content = try String(contentsOfFile: path, encoding: .utf8)
print("Read \(content.count) characters from: \(path)")
return content
}
// Read with encoding detection
static func readTextWithEncoding(from path: String) throws -> String {
let encodings: [String.Encoding] = [.utf8, .utf16, .ascii, .isoLatin1]
for encoding in encodings {
if let content = try? String(contentsOfFile: path, encoding: encoding) {
print("Read file with encoding: \(encoding)")
return content
}
}
throw NSError(domain: "ReadError", code: 1, userInfo: [
NSLocalizedDescriptionKey: "Could not read file with any supported encoding"
])
}
// Read line by line
static func readLines(from path: String) throws -> [String] {
let content = try readText(from: path)
let lines = content.components(separatedBy: .newlines)
print("Read \(lines.count) lines from: \(path)")
return lines
}
// Read with FileHandle (for large files)
static func readWithFileHandle(from path: String) throws -> String {
guard let fileHandle = FileHandle(forReadingAtPath: path) else {
throw NSError(domain: "ReadError", code: 1, userInfo: [
NSLocalizedDescriptionKey: "Failed to open file for reading"
])
}
defer { fileHandle.closeFile() }
let data = fileHandle.readDataToEndOfFile()
guard let content = String(data: data, encoding: .utf8) else {
throw NSError(domain: "ReadError", code: 2, userInfo: [
NSLocalizedDescriptionKey: "Failed to decode file data"
])
}
print("Read \(data.count) bytes using FileHandle")
return content
}
// Read specific range
static func readRange(from path: String, start: Int, length: Int) throws -> String {
guard let fileHandle = FileHandle(forReadingAtPath: path) else {
throw NSError(domain: "ReadError", code: 1, userInfo: [
NSLocalizedDescriptionKey: "Failed to open file for reading"
])
}
defer { fileHandle.closeFile() }
fileHandle.seek(toFileOffset: UInt64(start))
let data = fileHandle.readData(ofLength: length)
guard let content = String(data: data, encoding: .utf8) else {
throw NSError(domain: "ReadError", code: 2, userInfo: [
NSLocalizedDescriptionKey: "Failed to decode file data"
])
}
print("Read \(length) bytes from offset \(start)")
return content
}
}
// 3. File Info Helper
class FileInfoHelper {
static func getFileAttributes(at path: String) -> [FileAttributeKey: Any]? {
guard let attributes = try? FileManager.default.attributesOfItem(atPath: path) else {
return nil
}
return attributes
}
static func printFileInfo(at path: String) {
guard let attributes = getFileAttributes(at: path) else {
print("No file info available for: \(path)")
return
}
print("\n--- File Info: \(path) ---")
if let size = attributes[.size] as? UInt64 {
print("Size: \(size) bytes")
}
if let modified = attributes[.modificationDate] as? Date {
print("Modified: \(modified)")
}
if let type = attributes[.type] as? FileAttributeType {
print("Type: \(type.rawValue)")
}
}
}
// 4. Advanced Text Operations
class AdvancedTextOperations {
// Read file with line processing callback
static func processLinesInFile(at path: String, processor: (String, Int) -> Void) throws {
let lines = try TextFileReader.readLines(from: path)
for (index, line) in lines.enumerated() {
processor(line, index)
}
}
// Search for text in file
static func searchInFile(at path: String, searchText: String) -> [(line: String, lineNumber: Int)] {
guard let lines = try? TextFileReader.readLines(from: path) else {
return []
}
var results: [(String, Int)] = []
for (index, line) in lines.enumerated() {
if line.range(of: searchText, options: .caseInsensitive) != nil {
results.append((line, index + 1))
}
}
return results
}
// Replace text in file
static func replaceInFile(at path: String, searchText: String, replaceWith: String) throws {
var content = try TextFileReader.readText(from: path)
content = content.replacingOccurrences(of: searchText, with: replaceWith)
try TextFileWriter.writeText(to: path, text: content)
print("Replaced occurrences of '\(searchText)' in: \(path)")
}
// Count words in file
static func countWordsInFile(at path: String) -> Int {
guard let content = try? TextFileReader.readText(from: path) else {
return 0
}
let words = content.components(separatedBy: .whitespacesAndNewlines)
let nonEmptyWords = words.filter { !$0.isEmpty }
return nonEmptyWords.count
}
// Read and parse CSV
static func readCSV(at path: String) -> [[String]] {
guard let content = try? TextFileReader.readText(from: path) else {
return []
}
return content.components(separatedBy: .newlines).map { line in
line.components(separatedBy: ",")
}.filter { !$0.isEmpty }
}
// Write JSON to file
static func writeJSON<T: Encodable>(to path: String, object: T) throws {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
let data = try encoder.encode(object)
guard let jsonString = String(data: data, encoding: .utf8) else {
throw NSError(domain: "JSONError", code: 1, userInfo: [
NSLocalizedDescriptionKey: "Failed to convert JSON data to string"
])
}
try TextFileWriter.writeText(to: path, text: jsonString)
print("JSON written to: \(path)")
}
// Read JSON from file
static func readJSON<T: Decodable>(from path: String, type: T.Type) throws -> T {
let content = try TextFileReader.readText(from: path)
guard let data = content.data(using: .utf8) else {
throw NSError(domain: "JSONError", code: 1, userInfo: [
NSLocalizedDescriptionKey: "Failed to convert content to data"
])
}
let decoder = JSONDecoder()
let result = try decoder.decode(type, from: data)
print("JSON decoded from: \(path)")
return result
}
}
// 5. Safe File Operations with Error Handling
class SafeFileOperations {
enum FileError: Error, LocalizedError {
case fileNotFound(String)
case permissionDenied(String)
case invalidEncoding
case writeFailed(String)
var errorDescription: String? {
switch self {
case .fileNotFound(let path):
return "File not found: \(path)"
case .permissionDenied(let path):
return "Permission denied: \(path)"
case .invalidEncoding:
return "Invalid file encoding"
case .writeFailed(let path):
return "Failed to write to: \(path)"
}
}
}
// Safe read with comprehensive error handling
static func safeRead(from path: String) -> Result<String, FileError> {
guard FileManager.default.fileExists(atPath: path) else {
return .failure(.fileNotFound(path))
}
guard FileManager.default.isReadableFile(atPath: path) else {
return .failure(.permissionDenied(path))
}
do {
let content = try String(contentsOfFile: path, encoding: .utf8)
return .success(content)
} catch {
return .failure(.invalidEncoding)
}
}
// Safe write with backup
static func safeWrite(to path: String, text: String, createBackup: Bool = true) -> Result<Void, FileError> {
// Create backup if requested and file exists
if createBackup && FileManager.default.fileExists(atPath: path) {
let backupPath = path + ".backup"
do {
try FileManager.default.copyItem(atPath: path, toPath: backupPath)
print("Backup created at: \(backupPath)")
} catch {
return .failure(.writeFailed("Failed to create backup"))
}
}
do {
try text.write(toFile: path, atomically: true, encoding: .utf8)
return .success(())
} catch {
return .failure(.writeFailed(path))
}
}
}
// 6. Character Encoding Handler
class EncodingHandler {
// Detect file encoding
static func detectEncoding(at path: String) -> String.Encoding? {
let encodings: [String.Encoding] = [.utf8, .utf16, .utf16BigEndian, .utf32, .ascii]
for encoding in encodings {
if let _ = try? String(contentsOfFile: path, encoding: encoding) {
return encoding
}
}
return nil
}
// Convert file encoding
static func convertEncoding(at path: String, to targetEncoding: String.Encoding) throws {
// Read with current encoding
guard let currentEncoding = detectEncoding(at: path) else {
throw NSError(domain: "EncodingError", code: 1, userInfo: [
NSLocalizedDescriptionKey: "Could not detect source encoding"
])
}
let content = try String(contentsOfFile: path, encoding: currentEncoding)
// Write with new encoding
try content.write(toFile: path, atomically: true, encoding: targetEncoding)
print("Converted encoding from \(currentEncoding) to \(targetEncoding)")
}
}
// Main demonstration
func demonstrateTextFileOperations() {
print("=== macOS Swift Text File Read/Write Examples ===\n")
let testFile = "/tmp/test_text_file.txt"
let testContent = """
Hello, World!
This is a test file.
Created with Swift on macOS.
Line 4
Line 5
"""
// 1. Basic write
print("--- 1. Basic Write ---")
do {
try TextFileWriter.writeText(to: testFile, text: testContent)
} catch {
print("Error writing file: \(error)")
}
// 2. Basic read
print("\n--- 2. Basic Read ---")
do {
let content = try TextFileReader.readText(from: testFile)
print("Content:\n\(content)")
} catch {
print("Error reading file: \(error)")
}
// 3. Read lines
print("\n--- 3. Read Lines ---")
do {
let lines = try TextFileReader.readLines(from: testFile)
print("\(lines.count) lines:")
for (index, line) in lines.enumerated() {
print(" \(index + 1): \(line)")
}
} catch {
print("Error reading lines: \(error)")
}
// 4. Append text
print("\n--- 4. Append Text ---")
do {
try TextFileWriter.appendText(to: testFile, text: "Appended line!")
let content = try TextFileReader.readText(from: testFile)
print("Updated content (last line): \(content.components(separatedBy: .newlines).last ?? "")")
} catch {
print("Error appending: \(error)")
}
// 5. File info
print("\n--- 5. File Info ---")
FileInfoHelper.printFileInfo(at: testFile)
// 6. Word count
print("\n--- 6. Word Count ---")
let wordCount = AdvancedTextOperations.countWordsInFile(at: testFile)
print("Total words: \(wordCount)")
// 7. Search in file
print("\n--- 7. Search in File ---")
let results = AdvancedTextOperations.searchInFile(at: testFile, searchText: "Swift")
print("Found \(results.count) occurrences of 'Swift':")
for result in results {
print(" Line \(result.lineNumber): \(result.line)")
}
// 8. JSON operations
print("\n--- 8. JSON Operations ---")
struct User: Codable {
let name: String
let email: String
let age: Int
}
let user = User(name: "John Doe", email: "[email protected]", age: 30)
let jsonFile = "/tmp/user.json"
do {
try AdvancedTextOperations.writeJSON(to: jsonFile, object: user)
let decodedUser: User = try AdvancedTextOperations.readJSON(from: jsonFile, type: User.self)
print("Decoded: \(decodedUser.name) - \(decodedUser.email)")
} catch {
print("JSON error: \(error)")
}
// 9. Safe operations
print("\n--- 9. Safe Operations ---")
let result = SafeFileOperations.safeRead(from: testFile)
switch result {
case .success(let content):
print("Safe read successful: \(content.prefix(50))...")
case .failure(let error):
print("Safe read failed: \(error.localizedDescription)")
}
// 10. Encoding detection
print("\n--- 10. Encoding Detection ---")
if let encoding = EncodingHandler.detectEncoding(at: testFile) {
print("Detected encoding: \(encoding)")
}
print("\n=== All Text File Operations Completed ===")
}
// Run demonstration
demonstrateTextFileOperations()
💻 文件验证 swift
🟢 simple
⭐⭐⭐
检查文件是否存在,获取文件信息,验证文件类型,并比较文件
⏱️ 20 min
🏷️ swift, macos, file operations, validation
Prerequisites:
Basic Swift knowledge, Foundation framework
// macOS Swift File Validation Examples
// Using Foundation framework
import Foundation
// 1. Basic File Existence Check
class FileChecker {
// Check if file exists
static func fileExists(at path: String) -> Bool {
return FileManager.default.fileExists(atPath: path)
}
// Check if path is a file (not directory)
static func isFile(at path: String) -> Bool {
var isDirectory: ObjCBool = false
let exists = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory)
return exists && !isDirectory.boolValue
}
// Check if path is a directory
static func isDirectory(at path: String) -> Bool {
var isDirectory: ObjCBool = false
let exists = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory)
return exists && isDirectory.boolValue
}
// Check if file is readable
static func isReadable(at path: String) -> Bool {
return FileManager.default.isReadableFile(atPath: path)
}
// Check if file is writable
static func isWritable(at path: String) -> Bool {
return FileManager.default.isWritableFile(atPath: path)
}
// Check if file is deletable
static func isDeletable(at path: String) -> Bool {
return FileManager.default.isDeletableFile(atPath: path)
}
// Comprehensive file check
static func checkFile(at path: String) -> FileStatus {
let fileManager = FileManager.default
guard fileManager.fileExists(atPath: path) else {
return FileStatus(status: .notFound, isReadable: false, isWritable: false, isExecutable: false)
}
var isDirectory: ObjCBool = false
fileManager.fileExists(atPath: path, isDirectory: &isDirectory)
let isReadable = fileManager.isReadableFile(atPath: path)
let isWritable = fileManager.isWritableFile(atPath: path)
let isExecutable = fileManager.isExecutableFile(atPath: path)
let status: FileStatusType = isDirectory.boolValue ? .directory : .file
return FileStatus(status: status,
isReadable: isReadable,
isWritable: isWritable,
isExecutable: isExecutable)
}
}
// File Status Types
enum FileStatusType {
case notFound
case file
case directory
case symbolicLink
}
struct FileStatus {
let status: FileStatusType
let isReadable: Bool
let isWritable: Bool
let isExecutable: Bool
}
// 2. File Information
class FileInfo {
// Get complete file attributes
static func getAttributes(at path: String) -> [FileAttributeKey: Any]? {
return try? FileManager.default.attributesOfItem(atPath: path)
}
// Get file size
static func getSize(at path: String) -> UInt64? {
guard let attributes = getAttributes(at: path),
let size = attributes[.size] as? UInt64 else {
return nil
}
return size
}
// Get creation date
static func getCreationDate(at path: String) -> Date? {
guard let attributes = getAttributes(at: path),
let date = attributes[.creationDate] as? Date else {
return nil
}
return date
}
// Get modification date
static func getModificationDate(at path: String) -> Date? {
guard let attributes = getAttributes(at: path),
let date = attributes[.modificationDate] as? Date else {
return nil
}
return date
}
// Get file extension
static func getExtension(at path: String) -> String {
return (path as NSString).pathExtension
}
// Get file name without extension
static func getNameWithoutExtension(at path: String) -> String {
return (path as NSString).deletingPathExtension
}
// Get MIME type
static func getMIMEType(at path: String) -> String? {
let extension = getExtension(at: path)
let uti = UTTypeCreatePreferredIdentifierForTag(
kUTTagClassFilenameExtension,
extension as CFString,
nil
)?.takeRetainedValue() as String?
guard let typeID = uti else { return nil }
return UTTypeCopyPreferredTagWithClass(typeID as CFString, kUTTagClassMIMEType)?.takeRetainedValue() as String?
}
// Get detailed file info
static func getDetailedInfo(at path: String) -> DetailedFileInfo? {
guard let attributes = getAttributes(at: path) else {
return nil
}
return DetailedFileInfo(
path: path,
size: attributes[.size] as? UInt64 ?? 0,
creationDate: attributes[.creationDate] as? Date,
modificationDate: attributes[.modificationDate] as? Date,
fileType: attributes[.type] as? FileAttributeType,
posixPermissions: attributes[.posixPermissions] as? UInt16
)
}
}
// Detailed File Info Structure
struct DetailedFileInfo {
let path: String
let size: UInt64
let creationDate: Date?
let modificationDate: Date?
let fileType: FileAttributeType?
let posixPermissions: UInt16?
func printInfo() {
print("\n--- File Information ---")
print("Path: \(path)")
print("Size: \(formatSize(size))")
if let creationDate = creationDate {
print("Created: \(formatDate(creationDate))")
}
if let modificationDate = modificationDate {
print("Modified: \(formatDate(modificationDate))")
}
if let fileType = fileType {
print("Type: \(fileType.rawValue)")
}
if let permissions = posixPermissions {
print("Permissions: \(String(format: "0%o", permissions))")
}
}
private func formatSize(_ bytes: UInt64) -> String {
let kb = Double(bytes) / 1024
let mb = kb / 1024
let gb = mb / 1024
if gb >= 1 {
return String(format: "%.2f GB", gb)
} else if mb >= 1 {
return String(format: "%.2f MB", mb)
} else if kb >= 1 {
return String(format: "%.2f KB", kb)
} else {
return "\(bytes) bytes"
}
}
private func formatDate(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
return formatter.string(from: date)
}
}
// 3. File Type Validation
class FileTypeValidator {
// Validate by extension
static func isValidExtension(at path: String, validExtensions: [String]) -> Bool {
let ext = ((path as NSString).pathExtension).lowercased()
return validExtensions.contains(ext)
}
// Validate image file
static func isImageFile(at path: String) -> Bool {
let imageExtensions = ["jpg", "jpeg", "png", "gif", "bmp", "tiff", "webp", "ico"]
return isValidExtension(at: path, validExtensions: imageExtensions)
}
// Validate text file
static func isTextFile(at path: String) -> Bool {
let textExtensions = ["txt", "md", "html", "css", "js", "json", "xml", "csv"]
return isValidExtension(at: path, validExtensions: textExtensions)
}
// Validate by MIME type
static func hasMIMEType(at path: String, expectedMIME: String) -> Bool {
guard let mime = FileInfo.getMIMEType(at: path) else {
return false
}
return mime.caseInsensitiveCompare(expectedMIME) == .orderedSame
}
// Check if file is empty
static func isEmpty(at path: String) -> Bool {
guard let size = FileInfo.getSize(at: path) else {
return false
}
return size == 0
}
// Validate file size constraints
static func isSizeValid(at path: String, minSize: UInt64, maxSize: UInt64) -> Bool {
guard let size = FileInfo.getSize(at: path) else {
return false
}
return size >= minSize && size <= maxSize
}
}
// 4. File Comparison
class FileComparator {
// Compare files by size
static func compareSize(at path1: String, path2: String) -> ComparisonResult {
guard let size1 = FileInfo.getSize(at: path1),
let size2 = FileInfo.getSize(at: path2) else {
return .error
}
if size1 < size2 {
return .lessThan
} else if size1 > size2 {
return .greaterThan
} else {
return .equal
}
}
// Compare files by content
static func compareContent(at path1: String, path2: String) -> Bool {
guard let data1 = try? Data(contentsOf: URL(fileURLWithPath: path1)),
let data2 = try? Data(contentsOf: URL(fileURLWithPath: path2)) else {
return false
}
return data1 == data2
}
// Compare files by modification date
static func compareModificationDate(at path1: String, path2: String) -> ComparisonResult {
guard let date1 = FileInfo.getModificationDate(at: path1),
let date2 = FileInfo.getModificationDate(at: path2) else {
return .error
}
if date1 < date2 {
return .lessThan
} else if date1 > date2 {
return .greaterThan
} else {
return .equal
}
}
// Check if files are identical (size and content)
static func areIdentical(at path1: String, path2: String) -> Bool {
return compareSize(at: path1, path2: path2) == .equal &&
compareContent(at: path1, path2: path2)
}
// Compare using checksum
static func compareChecksum(at path1: String, path2: String) -> Bool {
guard let hash1 = calculateChecksum(at: path1),
let hash2 = calculateChecksum(at: path2) else {
return false
}
return hash1 == hash2
}
// Calculate file checksum (SHA256)
static func calculateChecksum(at path: String) -> String? {
guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else {
return nil
}
let hash = data.sha256()
return hash.map { String(format: "%02x", $0) }.joined()
}
}
// Comparison Result Enum
enum ComparisonResult {
case lessThan
case equal
case greaterThan
case error
}
// Data extension for hashing
extension Data {
func sha256() -> Data {
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
self.withUnsafeBytes {
_ = CC_SHA256($0.baseAddress, CC_LONG(self.count), &hash)
}
return Data(hash)
}
}
// 5. Path Validation
class PathValidator {
// Validate path format
static func isValidPath(_ path: String) -> Bool {
return !path.isEmpty && (path as NSString).isAbsolutePath
}
// Get absolute path from relative
static func getAbsolutePath(_ path: String) -> String? {
let fileManager = FileManager.default
return fileManager.absolutePath(forPath: path)
}
// Resolve path with tilde
static func resolveTilde(_ path: String) -> String {
return (path as NSString).expandingTildeInPath
}
// Validate file name
static func isValidFileName(_ name: String) -> Bool {
// Check for invalid characters
let invalidChars = CharacterSet(charactersIn: "\/:*?"<>|")
return name.rangeOfCharacter(from: invalidChars) == nil && !name.isEmpty
}
// Sanitize file name
static func sanitizeFileName(_ name: String) -> String {
let invalidChars = CharacterSet(charactersIn: "\/:*?"<>|")
return name.components(separatedBy: invalidChars).joined(separator: "_")
}
}
// 6. File Integrity Checker
class FileIntegrityChecker {
// Verify file hasn't been corrupted using stored hash
static func verifyIntegrity(at path: String, against expectedHash: String) -> Bool {
guard let actualHash = FileComparator.calculateChecksum(at: path) else {
return false
}
return actualHash.lowercased() == expectedHash.lowercased()
}
// Generate hash file for integrity checking
static func generateHashFile(at path: String) -> String? {
return FileComparator.calculateChecksum(at: path)
}
// Quick check (just size)
static func quickCheck(at path: String, expectedSize: UInt64) -> Bool {
guard let actualSize = FileInfo.getSize(at: path) else {
return false
}
return actualSize == expectedSize
}
}
// Main demonstration
func demonstrateFileValidation() {
print("=== macOS Swift File Validation Examples ===\n")
let testFile = "/tmp/validation_test.txt"
let testDir = "/tmp/validation_test_dir"
// Create test file
let fileManager = FileManager.default
try? "Test content for validation".write(toFile: testFile, atomically: true, encoding: .utf8)
try? fileManager.createDirectory(atPath: testDir, withIntermediateDirectories: true)
// 1. File existence checks
print("--- 1. File Existence Checks ---")
print("File exists: \(FileChecker.fileExists(at: testFile))")
print("Is file: \(FileChecker.isFile(at: testFile))")
print("Is directory: \(FileChecker.isDirectory(at: testDir))")
print("Is readable: \(FileChecker.isReadable(at: testFile))")
print("Is writable: \(FileChecker.isWritable(at: testFile))")
// 2. Comprehensive file check
print("\n--- 2. Comprehensive File Check ---")
let status = FileChecker.checkFile(at: testFile)
print("Status: \(status.status)")
print("Readable: \(status.isReadable)")
print("Writable: \(status.isWritable)")
print("Executable: \(status.isExecutable)")
// 3. File information
print("\n--- 3. File Information ---")
if let size = FileInfo.getSize(at: testFile) {
print("Size: \(size) bytes")
}
if let modDate = FileInfo.getModificationDate(at: testFile) {
print("Modified: \(modDate)")
}
print("Extension: \(FileInfo.getExtension(at: testFile))")
print("Name without ext: \(FileInfo.getNameWithoutExtension(at: testFile))")
// 4. Detailed info
print("\n--- 4. Detailed File Info ---")
if let info = FileInfo.getDetailedInfo(at: testFile) {
info.printInfo()
}
// 5. File type validation
print("\n--- 5. File Type Validation ---")
print("Is text file: \(FileTypeValidator.isTextFile(at: testFile))")
print("Is image file: \(FileTypeValidator.isImageFile(at: testFile))")
print("Is empty: \(FileTypeValidator.isEmpty(at: testFile))")
print("Size valid (1-1000 bytes): \(FileTypeValidator.isSizeValid(at: testFile, minSize: 1, maxSize: 1000))")
// 6. File comparison
print("\n--- 6. File Comparison ---")
let testFile2 = "/tmp/validation_test2.txt"
try? "Different content".write(toFile: testFile2, atomically: true, encoding: .utf8)
let sizeCompare = FileComparator.compareSize(at: testFile, path2: testFile2)
print("Size comparison: \(sizeCompare)")
let contentEqual = FileComparator.compareContent(at: testFile, path2: testFile)
print("Content equal: \(contentEqual)")
// 7. Checksum
print("\n--- 7. Checksum ---")
if let checksum = FileComparator.calculateChecksum(at: testFile) {
print("SHA256: \(checksum)")
// Verify integrity
let valid = FileIntegrityChecker.verifyIntegrity(at: testFile, against: checksum)
print("Integrity check: \(valid ? "PASSED" : "FAILED")")
}
// 8. Path validation
print("\n--- 8. Path Validation ---")
let relativePath = "./test.txt"
print("Is absolute path: \(PathValidator.isValidPath(testFile))")
print("Resolved absolute path: \(PathValidator.getAbsolutePath(relativePath) ?? "None")")
print("Valid filename 'test.txt': \(PathValidator.isValidFileName("test.txt"))")
print("Valid filename 'test?.txt': \(PathValidator.isValidFileName("test?.txt"))")
print("Sanitized name: \(PathValidator.sanitizeFileName("test?.txt"))")
// Cleanup
try? fileManager.removeItem(atPath: testFile)
try? fileManager.removeItem(atPath: testFile2)
try? fileManager.removeItem(atPath: testDir)
print("\nCleanup completed")
print("\n=== All File Validation Examples Completed ===")
}
// Run demonstration
demonstrateFileValidation()
💻 文件复制/移动 swift
🟡 intermediate
⭐⭐⭐
使用覆盖选项、进度跟踪和错误恢复复制和移动文件
⏱️ 25 min
🏷️ swift, macos, file operations
Prerequisites:
Basic Swift knowledge, Foundation framework
// macOS Swift File Copy/Move Examples
// Using Foundation framework
import Foundation
// 1. Basic File Copy
class FileCopier {
// Simple copy
static func copyFile(from source: String, to destination: String) throws {
try FileManager.default.copyItem(atPath: source, toPath: destination)
print("Copied: \(source) -> \(destination)")
}
// Copy with overwrite option
static func copyFile(from source: String, to destination: String, overwrite: Bool) throws {
let fileManager = FileManager.default
// Check if destination exists
if fileManager.fileExists(atPath: destination) {
if overwrite {
// Remove existing file
try fileManager.removeItem(atPath: destination)
print("Removed existing file: \(destination)")
} else {
throw FileError.fileAlreadyExists(destination)
}
}
try copyFile(from: source, to: destination)
}
// Copy to directory
static func copyFileToDirectory(from source: String, directory: String) throws {
let fileName = (source as NSString).lastPathComponent
let destination = (directory as NSString).appendingPathComponent(fileName)
try copyFile(from: source, to: destination)
}
// Copy with URL
static func copyFile(from sourceURL: URL, to destinationURL: URL) throws {
let fileManager = FileManager.default
// Create destination directory if needed
let destDir = destinationURL.deletingLastPathComponent().path
if !fileManager.fileExists(atPath: destDir) {
try fileManager.createDirectory(atPath: destDir, withIntermediateDirectories: true)
}
try fileManager.copyItem(at: sourceURL, to: destinationURL)
print("Copied: \(sourceURL.path) -> \(destinationURL.path)")
}
// Copy with progress callback
static func copyFileWithProgress(from source: String, to destination: String,
progress: ((Double) -> Void)?) throws {
let fileManager = FileManager.default
guard fileManager.fileExists(atPath: source) else {
throw FileError.fileNotFound(source)
}
let attributes = try fileManager.attributesOfItem(atPath: source)
guard let fileSize = attributes[.size] as? UInt64 else {
throw FileError.invalidFile
}
// Read source in chunks
guard let sourceHandle = FileHandle(forReadingAtPath: source) else {
throw FileError.readFailed(source)
}
// Create destination
fileManager.createFile(atPath: destination, contents: nil)
guard let destHandle = FileHandle(forWritingAtPath: destination) else {
throw FileError.writeFailed(destination)
}
let chunkSize = 1024 * 1024 // 1MB chunks
var bytesCopied: UInt64 = 0
defer {
sourceHandle.closeFile()
destHandle.closeFile()
}
while true {
let data = sourceHandle.readData(ofLength: chunkSize)
if data.isEmpty { break }
destHandle.write(data)
bytesCopied += UInt64(data.count)
let percent = Double(bytesCopied) / Double(fileSize)
progress?(percent)
}
print("Copy completed: \(destination)")
}
}
// 2. Basic File Move
class FileMover {
// Simple move
static func moveFile(from source: String, to destination: String) throws {
try FileManager.default.moveItem(atPath: source, toPath: destination)
print("Moved: \(source) -> \(destination)")
}
// Move with overwrite
static func moveFile(from source: String, to destination: String, overwrite: Bool) throws {
let fileManager = FileManager.default
if fileManager.fileExists(atPath: destination) {
if overwrite {
try fileManager.removeItem(atPath: destination)
print("Removed existing file: \(destination)")
} else {
throw FileError.fileAlreadyExists(destination)
}
}
try moveFile(from: source, to: destination)
}
// Move to directory
static func moveFileToDirectory(from source: String, directory: String) throws {
let fileName = (source as NSString).lastPathComponent
let destination = (directory as NSString).appendingPathComponent(fileName)
try moveFile(from: source, to: destination)
}
// Safe move with backup
static func moveFileWithBackup(from source: String, to destination: String) throws {
let fileManager = FileManager.default
// Create backup if destination exists
if fileManager.fileExists(atPath: destination) {
let backupPath = destination + ".backup"
try fileManager.moveItem(atPath: destination, toPath: backupPath)
print("Backup created: \(backupPath)")
}
try moveFile(from: source, to: destination)
}
// Move with URL
static func moveFile(from sourceURL: URL, to destinationURL: URL) throws {
let fileManager = FileManager.default
// Create destination directory if needed
let destDir = destinationURL.deletingLastPathComponent().path
if !fileManager.fileExists(atPath: destDir) {
try fileManager.createDirectory(atPath: destDir, withIntermediateDirectories: true)
}
try fileManager.moveItem(at: sourceURL, to: destinationURL)
print("Moved: \(sourceURL.path) -> \(destinationURL.path)")
}
}
// 3. Batch Operations
class BatchFileOperations {
// Copy multiple files
static func copyFiles(sources: [String], to destinationDirectory: String) throws {
let fileManager = FileManager.default
// Ensure destination directory exists
if !fileManager.fileExists(atPath: destinationDirectory) {
try fileManager.createDirectory(atPath: destinationDirectory,
withIntermediateDirectories: true)
}
var successCount = 0
var failureCount = 0
for source in sources {
do {
let fileName = (source as NSString).lastPathComponent
let destination = (destinationDirectory as NSString).appendingPathComponent(fileName)
try FileCopier.copyFile(from: source, to: destination, overwrite: true)
successCount += 1
} catch {
print("Failed to copy \(source): \(error)")
failureCount += 1
}
}
print("Batch copy completed: \(successCount) succeeded, \(failureCount) failed")
}
// Move multiple files
static func moveFiles(sources: [String], to destinationDirectory: String) throws {
let fileManager = FileManager.default
if !fileManager.fileExists(atPath: destinationDirectory) {
try fileManager.createDirectory(atPath: destinationDirectory,
withIntermediateDirectories: true)
}
var successCount = 0
var failureCount = 0
for source in sources {
do {
let fileName = (source as NSString).lastPathComponent
let destination = (destinationDirectory as NSString).appendingPathComponent(fileName)
try FileMover.moveFile(from: source, to: destination, overwrite: true)
successCount += 1
} catch {
print("Failed to move \(source): \(error)")
failureCount += 1
}
}
print("Batch move completed: \(successCount) succeeded, \(failureCount) failed")
}
// Copy directory recursively
static func copyDirectory(from source: String, to destination: String) throws {
let fileManager = FileManager.default
var isDirectory: ObjCBool = false
guard fileManager.fileExists(atPath: source, isDirectory: &isDirectory),
isDirectory.boolValue else {
throw FileError.notADirectory(source)
}
try fileManager.copyItem(atPath: source, toPath: destination)
print("Directory copied: \(source) -> \(destination)")
}
// Move directory
static func moveDirectory(from source: String, to destination: String) throws {
let fileManager = FileManager.default
var isDirectory: ObjCBool = false
guard fileManager.fileExists(atPath: source, isDirectory: &isDirectory),
isDirectory.boolValue else {
throw FileError.notADirectory(source)
}
try fileManager.moveItem(atPath: source, toPath: destination)
print("Directory moved: \(source) -> \(destination)")
}
}
// 4. File Error Types
enum FileError: Error, LocalizedError {
case fileNotFound(String)
case fileAlreadyExists(String)
case permissionDenied(String)
case notADirectory(String)
case invalidFile
case readFailed(String)
case writeFailed(String)
var errorDescription: String? {
switch self {
case .fileNotFound(let path):
return "File not found: \(path)"
case .fileAlreadyExists(let path):
return "File already exists: \(path)"
case .permissionDenied(let path):
return "Permission denied: \(path)"
case .notADirectory(let path):
return "Not a directory: \(path)"
case .invalidFile:
return "Invalid file"
case .readFailed(let path):
return "Failed to read: \(path)"
case .writeFailed(let path):
return "Failed to write: \(path)"
}
}
}
// 5. Smart File Operations
class SmartFileOperations {
// Copy with duplicate handling
static func smartCopy(from source: String, to destination: String) throws {
let fileManager = FileManager.default
if fileManager.fileExists(atPath: destination) {
// Add number suffix
let baseName = (destination as NSString).deletingPathExtension
let extensionName = (destination as NSString).pathExtension
var counter = 1
var newPath = destination
while fileManager.fileExists(atPath: newPath) {
let newBaseName = "\(baseName) \(counter)"
newPath = (newBaseName as NSString).appendingPathExtension(extensionName) ?? newBaseName
counter += 1
}
print("Destination exists, using: \(newPath)")
try FileCopier.copyFile(from: source, to: newPath)
} else {
try FileCopier.copyFile(from: source, to: destination)
}
}
// Safe move with undo
static func moveWithUndo(from source: String, to destination: String) throws -> () -> Void {
let fileManager = FileManager.default
// Remember original location for undo
let originalSource = source
if fileManager.fileExists(atPath: destination) {
try fileManager.removeItem(atPath: destination)
}
try FileMover.moveFile(from: source, to: destination)
// Return undo function
return {
do {
try FileMover.moveFile(from: destination, to: originalSource)
print("Undo: Moved back to \(originalSource)")
} catch {
print("Undo failed: \(error)")
}
}
}
// Copy with file verification
static func verifiedCopy(from source: String, to destination: String) throws {
let sourceAttrs = FileManager.default.attributesOfItem(atPath: source)
try FileCopier.copyFile(from: source, to: destination, overwrite: true)
// Verify by comparing file sizes
let destAttrs = FileManager.default.attributesOfItem(atPath: destination)
guard let sourceSize = sourceAttrs[.size] as? UInt64,
let destSize = destAttrs[.size] as? UInt64,
sourceSize == destSize else {
throw FileError.writeFailed(destination)
}
print("Verified copy: \(sourceSize) bytes")
}
}
// 6. File Operation with Attributes
class FileOperationWithAttributes {
// Copy preserving attributes
static func copyWithAttributes(from source: String, to destination: String) throws {
let fileManager = FileManager.default
// Get source attributes
guard let attributes = try? fileManager.attributesOfItem(atPath: source) else {
throw FileError.invalidFile
}
// Copy file
try FileCopier.copyFile(from: source, to: destination, overwrite: true)
// Apply attributes
try fileManager.setAttributes(attributes, ofItemAtPath: destination)
print("Copied with attributes preserved")
}
// Copy with permission change
static func copyWithPermissions(from source: String, to destination: String,
permissions: FilePermissions) throws {
try FileCopier.copyFile(from: source, to: destination, overwrite: true)
let fileManager = FileManager.default
try fileManager.setAttributes([.posixPermissions: permissions.rawValue],
ofItemAtPath: destination)
print("Copied with permissions: \(permissions.rawValue)")
}
}
// Main demonstration
func demonstrateFileCopyMove() {
print("=== macOS Swift File Copy/Move Examples ===\n")
let tempDir = "/tmp/file_operations_test"
let sourceFile = "/tmp/source_test.txt"
let destFile1 = "/tmp/dest_test1.txt"
let destFile2 = "/tmp/dest_test2.txt"
// Create test file
let fileManager = FileManager.default
try? fileManager.createDirectory(atPath: tempDir, withIntermediateDirectories: true)
try? "Test content for copy/move".write(toFile: sourceFile, atomically: true, encoding: .utf8)
// 1. Simple copy
print("--- 1. Simple Copy ---")
do {
try FileCopier.copyFile(from: sourceFile, to: destFile1)
} catch {
print("Error: \(error)")
}
// 2. Copy with progress
print("\n--- 2. Copy with Progress ---")
do {
try FileCopier.copyFileWithProgress(from: sourceFile, to: destFile2) { progress in
print("Progress: \(Int(progress * 100))%")
}
} catch {
print("Error: \(error)")
}
// 3. Simple move
print("\n--- 3. Simple Move ---")
let moveSource = "/tmp/move_source.txt"
let moveDest = "/tmp/move_dest.txt"
try? "File to move".write(toFile: moveSource, atomically: true, encoding: .utf8)
do {
try FileMover.moveFile(from: moveSource, to: moveDest)
} catch {
print("Error: \(error)")
}
// 4. Smart copy (handle duplicates)
print("\n--- 4. Smart Copy ---")
let smartSource = "/tmp/smart_source.txt"
let smartDest = "/tmp/smart_dest.txt"
try? "Smart copy test".write(toFile: smartSource, atomically: true, encoding: .utf8)
do {
try SmartFileOperations.smartCopy(from: smartSource, to: smartDest)
try SmartFileOperations.smartCopy(from: smartSource, to: smartDest) // Will create duplicate
} catch {
print("Error: \(error)")
}
// 5. Batch copy
print("\n--- 5. Batch Copy ---")
let files = (1...3).map { "/tmp/batch_source\($0).txt" }
files.forEach { try? "Batch file \($0)".write(toFile: $0, atomically: true, encoding: .utf8) }
do {
try BatchFileOperations.copyFiles(sources: files, to: tempDir)
} catch {
print("Error: \(error)")
}
// 6. Verified copy
print("\n--- 6. Verified Copy ---")
let verifySource = "/tmp/verify_source.txt"
let verifyDest = "/tmp/verify_dest.txt"
try? "Verification test".write(toFile: verifySource, atomically: true, encoding: .utf8)
do {
try SmartFileOperations.verifiedCopy(from: verifySource, to: verifyDest)
} catch {
print("Error: \(error)")
}
// 7. Move with undo
print("\n--- 7. Move with Undo ---")
let undoSource = "/tmp/undo_source.txt"
let undoDest = "/tmp/undo_dest.txt"
try? "Undo test".write(toFile: undoSource, atomically: true, encoding: .utf8)
do {
let undo = try SmartFileOperations.moveWithUndo(from: undoSource, to: undoDest)
print("File moved, executing undo...")
undo()
} catch {
print("Error: \(error)")
}
// Cleanup
try? fileManager.removeItem(atPath: tempDir)
print("\nCleanup completed")
print("\n=== All File Copy/Move Examples Completed ===")
}
// Run demonstration
demonstrateFileCopyMove()
💻 目录遍历 swift
🟡 intermediate
⭐⭐⭐⭐
递归遍历目录,使用过滤器列出文件,并搜索特定文件类型
⏱️ 30 min
🏷️ swift, macos, file operations, directory
Prerequisites:
Intermediate Swift, Foundation framework
// macOS Swift Directory Traversal Examples
// Using Foundation framework
import Foundation
// 1. Basic Directory Listing
class DirectoryLister {
// List all items in directory
static func listDirectory(at path: String) throws -> [String] {
let fileManager = FileManager.default
guard let contents = try? fileManager.contentsOfDirectory(atPath: path) else {
throw DirectoryError.notADirectory(path)
}
return contents
}
// List with details
static func listDirectoryWithDetails(at path: String) throws -> [FileItem] {
let fileManager = FileManager.default
var items: [FileItem] = []
guard let contents = try? fileManager.contentsOfDirectory(atPath: path) else {
throw DirectoryError.notADirectory(path)
}
for item in contents {
let itemPath = (path as NSString).appendingPathComponent(item)
let isDirectory = (try? itemPath.isDirectory()) ?? false
var fileItem = FileItem(name: item, path: itemPath, isDirectory: isDirectory)
if let attributes = try? fileManager.attributesOfItem(atPath: itemPath) {
fileItem.size = attributes[.size] as? UInt64 ?? 0
fileItem.modifiedDate = attributes[.modificationDate] as? Date
}
items.append(fileItem)
}
return items
}
// List only files
static func listFiles(at path: String) throws -> [String] {
let contents = try listDirectory(path)
return contents.filter { item in
let itemPath = (path as NSString).appendingPathComponent(item)
var isDirectory: ObjCBool = false
FileManager.default.fileExists(atPath: itemPath, isDirectory: &isDirectory)
return !isDirectory.boolValue
}
}
// List only subdirectories
static func listSubdirectories(at path: String) throws -> [String] {
let contents = try listDirectory(path)
return contents.filter { item in
let itemPath = (path as NSString).appendingPathComponent(item)
var isDirectory: ObjCBool = false
FileManager.default.fileExists(atPath: itemPath, isDirectory: &isDirectory)
return isDirectory.boolValue
}
}
}
// File Item Structure
struct FileItem {
let name: String
let path: String
let isDirectory: Bool
var size: UInt64 = 0
var modifiedDate: Date?
}
// Directory Error Types
enum DirectoryError: Error, LocalizedError {
case notADirectory(String)
case directoryNotFound(String)
case accessDenied(String)
var errorDescription: String? {
switch self {
case .notADirectory(let path):
return "Not a directory: \(path)"
case .directoryNotFound(let path):
return "Directory not found: \(path)"
case .accessDenied(let path):
return "Access denied: \(path)"
}
}
}
// 2. Recursive Directory Traversal
class DirectoryTraverser {
// Recursively find all files
static func findAllFiles(at path: String) -> [String] {
var files: [String] = []
let fileManager = FileManager.default
guard let contents = try? fileManager.contentsOfDirectory(atPath: path) else {
return files
}
for item in contents {
let itemPath = (path as NSString).appendingPathComponent(item)
var isDirectory: ObjCBool = false
if fileManager.fileExists(atPath: itemPath, isDirectory: &isDirectory) {
if isDirectory.boolValue {
// Recurse into subdirectory
files.append(contentsOf: findAllFiles(at: itemPath))
} else {
files.append(itemPath)
}
}
}
return files
}
// Recursive traversal with callback
static func traverseDirectory(at path: String,
callback: (String, Bool, Int) -> Void) {
traverseDirectory(at: path, depth: 0, callback: callback)
}
private static func traverseDirectory(at path: String, depth: Int,
callback: (String, Bool, Int) -> Void) {
let fileManager = FileManager.default
guard let contents = try? fileManager.contentsOfDirectory(atPath: path) else {
return
}
for item in contents {
let itemPath = (path as NSString).appendingPathComponent(item)
var isDirectory: ObjCBool = false
if fileManager.fileExists(atPath: itemPath, isDirectory: &isDirectory) {
callback(itemPath, isDirectory.boolValue, depth)
if isDirectory.boolValue {
traverseDirectory(at: itemPath, depth: depth + 1, callback: callback)
}
}
}
}
// Get directory tree
static func getDirectoryTree(at path: String, maxDepth: Int = Int.max) -> DirectoryNode {
let root = DirectoryNode(name: (path as NSString).lastPathComponent,
path: path,
isDirectory: true,
depth: 0)
buildTree(for: root, maxDepth: maxDepth)
return root
}
private static func buildTree(for node: DirectoryNode, maxDepth: Int) {
guard node.depth < maxDepth else { return }
let fileManager = FileManager.default
guard let contents = try? fileManager.contentsOfDirectory(atPath: node.path) else {
return
}
for item in contents.sorted() {
let itemPath = (node.path as NSString).appendingPathComponent(item)
var isDirectory: ObjCBool = false
if fileManager.fileExists(atPath: itemPath, isDirectory: &isDirectory) {
let childNode = DirectoryNode(name: item,
path: itemPath,
isDirectory: isDirectory.boolValue,
depth: node.depth + 1)
node.children.append(childNode)
if isDirectory.boolValue {
buildTree(for: childNode, maxDepth: maxDepth)
}
}
}
}
}
// Directory Tree Node
class DirectoryNode {
let name: String
let path: String
let isDirectory: Bool
let depth: Int
var children: [DirectoryNode] = []
init(name: String, path: String, isDirectory: Bool, depth: Int) {
self.name = name
self.path = path
self.isDirectory = isDirectory
self.depth = depth
}
func printTree() {
let indent = String(repeating: " ", count: depth)
let prefix = isDirectory ? "📁" : "📄"
print("\(indent)\(prefix) \(name)")
for child in children {
child.printTree()
}
}
}
// 3. File Filtering
class FileFilter {
// Filter by extension
static func filterByExtension(in path: String, extensions: [String]) -> [String] {
let files = DirectoryTraverser.findAllFiles(at: path)
return files.filter { filePath in
let ext = (filePath as NSString).pathExtension.lowercased()
return extensions.contains(ext)
}
}
// Filter by name pattern
static func filterByNamePattern(in path: String, pattern: String) -> [String] {
let files = DirectoryTraverser.findAllFiles(at: path)
return files.filter { filePath in
let fileName = (filePath as NSString).lastPathComponent
return fileName.range(of: pattern, options: .regularExpression) != nil
}
}
// Filter by size
static func filterBySize(in path: String, minSize: UInt64, maxSize: UInt64) -> [String] {
let fileManager = FileManager.default
let files = DirectoryTraverser.findAllFiles(at: path)
return files.filter { filePath in
guard let attributes = try? fileManager.attributesOfItem(atPath: filePath),
let size = attributes[.size] as? UInt64 else {
return false
}
return size >= minSize && size <= maxSize
}
}
// Filter by date
static func filterByModifiedDate(in path: String,
after startDate: Date,
before endDate: Date) -> [String] {
let fileManager = FileManager.default
let files = DirectoryTraverser.findAllFiles(at: path)
return files.filter { filePath in
guard let attributes = try? fileManager.attributesOfItem(atPath: filePath),
let modifiedDate = attributes[.modificationDate] as? Date else {
return false
}
return modifiedDate >= startDate && modifiedDate <= endDate
}
}
// Find duplicates by size
static func findPotentialDuplicates(in path: String) -> [[String]] {
let fileManager = FileManager.default
let files = DirectoryTraverser.findAllFiles(at: path)
var sizeGroups: [UInt64: [String]] = [:]
for filePath in files {
guard let attributes = try? fileManager.attributesOfItem(atPath: filePath),
let size = attributes[.size] as? UInt64 else {
continue
}
if sizeGroups[size] == nil {
sizeGroups[size] = []
}
sizeGroups[size]?.append(filePath)
}
// Return groups with more than one file
return sizeGroups.values.filter { $0.count > 1 }
}
}
// 4. Directory Statistics
class DirectoryStatistics {
struct Stats {
var totalFiles: Int = 0
var totalDirectories: Int = 0
var totalSize: UInt64 = 0
var largestFile: (path: String, size: UInt64)?
var extensionCounts: [String: Int] = [:]
}
static func calculateStatistics(for path: String) -> Stats {
var stats = Stats()
DirectoryTraverser.traverseDirectory(at: path) { filePath, isDirectory, _ in
if isDirectory {
stats.totalDirectories += 1
} else {
stats.totalFiles += 1
let fileManager = FileManager.default
if let attributes = try? fileManager.attributesOfItem(atPath: filePath),
let size = attributes[.size] as? UInt64 {
stats.totalSize += size
// Track largest file
if stats.largestFile == nil || size > stats.largestFile!.size {
stats.largestFile = (filePath, size)
}
// Count extensions
let ext = ((filePath as NSString).pathExtension).lowercased()
let extKey = ext.isEmpty ? "(no extension)" : ext
stats.extensionCounts[extKey, default: 0] += 1
}
}
}
return stats
}
static func printStatistics(_ stats: Stats) {
print("\n=== Directory Statistics ===")
print("Total files: \(stats.totalFiles)")
print("Total directories: \(stats.totalDirectories)")
print("Total size: \(formatBytes(stats.totalSize))")
if let largest = stats.largestFile {
print("Largest file: \((largest.path as NSString).lastPathComponent)")
print(" Size: \(formatBytes(largest.size))")
}
print("\nExtension counts:")
for (ext, count) in stats.extensionCounts.sorted(by: { $0.value > $1.value }) {
print(" .\(ext): \(count)")
}
}
private static func formatBytes(_ bytes: UInt64) -> String {
let kb = Double(bytes) / 1024
let mb = kb / 1024
let gb = mb / 1024
if gb >= 1 {
return String(format: "%.2f GB", gb)
} else if mb >= 1 {
return String(format: "%.2f MB", mb)
} else if kb >= 1 {
return String(format: "%.2f KB", kb)
} else {
return "\(bytes) bytes"
}
}
}
// 5. File Searcher
class FileSearcher {
// Search files by name
static func searchByName(in path: String, name: String) -> [String] {
let files = DirectoryTraverser.findAllFiles(at: path)
return files.filter { filePath in
let fileName = (filePath as NSString).lastPathComponent
return fileName.range(of: name, options: .caseInsensitive) != nil
}
}
// Search files containing text
static func searchByContent(in path: String, searchText: String) -> [String] {
let files = DirectoryTraverser.findAllFiles(at: path)
var matches: [String] = []
for filePath in files {
if let content = try? String(contentsOfFile: filePath, encoding: .utf8) {
if content.range(of: searchText, options: .caseInsensitive) != nil {
matches.append(filePath)
}
}
}
return matches
}
// Find recently modified files
static func findRecentlyModified(in path: String, within seconds: TimeInterval) -> [String] {
let fileManager = FileManager.default
let files = DirectoryTraverser.findAllFiles(at: path)
let cutoffDate = Date().addingTimeInterval(-seconds)
return files.filter { filePath in
guard let attributes = try? fileManager.attributesOfItem(atPath: filePath),
let modifiedDate = attributes[.modificationDate] as? Date else {
return false
}
return modifiedDate > cutoffDate
}
}
}
// 6. Path Helper Extension
extension String {
func isDirectory() throws -> Bool {
var isDirectory: ObjCBool = false
FileManager.default.fileExists(atPath: self, isDirectory: &isDirectory)
return isDirectory.boolValue
}
var fileExists: Bool {
FileManager.default.fileExists(atPath: self)
}
}
// Main demonstration
func demonstrateDirectoryTraversal() {
print("=== macOS Swift Directory Traversal Examples ===\n")
let testDir = "/tmp/directory_traversal_test"
let fileManager = FileManager.default
// Create test directory structure
try? fileManager.createDirectory(atPath: testDir, withIntermediateDirectories: true)
// Create some test files
try? "File 1".write(toFile: "\(testDir)/file1.txt", atomically: true, encoding: .utf8)
try? "File 2".write(toFile: "\(testDir)/file2.md", atomically: true, encoding: .utf8)
// Create subdirectory
let subDir = "\(testDir)/subdir"
try? fileManager.createDirectory(atPath: subDir, withIntermediateDirectories: true)
try? "Subfile 1".write(toFile: "\(subDir)/subfile1.txt", atomically: true, encoding: .utf8)
// 1. List directory
print("--- 1. List Directory ---")
do {
let items = try DirectoryLister.listDirectory(at: testDir)
print("Items in \(testDir):")
for item in items {
print(" - \(item)")
}
} catch {
print("Error: \(error)")
}
// 2. List with details
print("\n--- 2. List with Details ---")
do {
let items = try DirectoryLister.listDirectoryWithDetails(at: testDir)
print("Files in \(testDir):")
for item in items {
let type = item.isDirectory ? "DIR " : "FILE"
print(" [\(type)] \(item.name) - \(item.size) bytes")
}
} catch {
print("Error: \(error)")
}
// 3. Recursive traversal
print("\n--- 3. Recursive Traversal ---")
let allFiles = DirectoryTraverser.findAllFiles(at: testDir)
print("All files:")
for file in allFiles {
print(" - \(file)")
}
// 4. Directory tree
print("\n--- 4. Directory Tree ---")
let tree = DirectoryTraverser.getDirectoryTree(at: testDir, maxDepth: 2)
tree.printTree()
// 5. Filter by extension
print("\n--- 5. Filter by Extension ---")
let txtFiles = FileFilter.filterByExtension(in: testDir, extensions: ["txt"])
print("Text files:")
for file in txtFiles {
print(" - \(file)")
}
// 6. Directory statistics
print("\n--- 6. Directory Statistics ---")
let stats = DirectoryStatistics.calculateStatistics(for: testDir)
DirectoryStatistics.printStatistics(stats)
// 7. Search by name
print("\n--- 7. Search by Name ---")
let searchResults = FileSearcher.searchByName(in: testDir, name: "file")
print("Files containing 'file':")
for result in searchResults {
print(" - \(result)")
}
// Cleanup
try? fileManager.removeItem(atPath: testDir)
print("\nCleanup completed")
print("\n=== All Directory Traversal Examples Completed ===")
}
// Run demonstration
demonstrateDirectoryTraversal()