I decided to dig deeper into the process of showing duplicates. I wrote a Swift command-line script that takes a command-line folder path, or without that path, it opens an NSOpenPanel to allow the folder selection. It is recursive in that designated folder and will list duplicates organized by matching checksum. It cleanly compiles and one can reference that binary in a Shortcut Run Shell Script.
First, the Shortcut, where I selected the Desktop folder:
and the Swift 6 source (built with command-line tools for Xcode 27 on macOS 27:
import Foundation
import AppKit
import CryptoKit
/*
dupes.swift
xcrun --sdk macosx swiftc -Osize dupes.swift
Reports duplicate checksums as ascending groups with duplicate
files subordinate and their ascending modification dates.
Build: xcrun --sdk macosx swiftc -Osize dupes.swift
*/
class Record: NSObject {
@objc var chksum: String
@objc var fsize: String
@objc var mdate: String
@objc var fpath: String
init(chksum: String, fsize: String, mdate: String, fpath: String) {
self.chksum = chksum
self.fsize = fsize
self.mdate = mdate
self.fpath = fpath
}
override var description: String {
return "Record(chksum: \(chksum), fsize: \(fsize), cdate: \(mdate), fpath: \(fpath))"
}
}
func formatBytes(nbr: Int64) -> String {
// automatically uses appropriate SI storage abbreviation for locale
let autoLocale = Locale.autoupdatingCurrent.identifier
let style = ByteCountFormatStyle(style: .file,
allowedUnits: [.all],
spellsOutZero: true,
includesActualByteCount: false,
locale: Locale(identifier: autoLocale))
return style.format(nbr)
}
public func fileChooser() -> String {
let openPanel = NSOpenPanel()
openPanel.isFloatingPanel = true
openPanel.setFrame(NSRect(x: 0, y: 0, width: 800, height: 525), display: true)
openPanel.allowsMultipleSelection = false
openPanel.canChooseDirectories = true
openPanel.canCreateDirectories = false
openPanel.canChooseFiles = false
openPanel.allowedContentTypes = [.folder]
guard openPanel.runModal() == .OK else {
return "User Canceled"
}
return openPanel.url!.path
}
let myData: NSMutableArray = []
let inputArgs: [URL]
var folderURL: URL
var isDirectory: ObjCBool = false
inputArgs = CommandLine.arguments.dropFirst().map { URL(fileURLWithPath: $0).standardizedFileURL }
if inputArgs.isEmpty {
folderURL = URL(fileURLWithPath: fileChooser())
} else {
folderURL = inputArgs[0]
}
let fmr = FileManager.default
let exists = fmr.fileExists(atPath: folderURL.path, isDirectory: &isDirectory)
guard exists, isDirectory.boolValue else {
print("The selection must be a folder")
exit(EXIT_FAILURE)
}
// let fileURL = URL(fileURLWithPath: ("~/Desktop/" as NSString).expandingTildeInPath)
let keys: [URLResourceKey] = [.totalFileSizeKey, .contentModificationDateKey, .isDirectoryKey]
let opts: FileManager.DirectoryEnumerationOptions = [.skipsHiddenFiles, .skipsPackageDescendants]
let enumerator = fmr.enumerator(at: folderURL, includingPropertiesForKeys: keys, options: opts,
errorHandler: (nil))
for case let fileURL as URL in enumerator! {
do {
let resourceValues = try fileURL.resourceValues(forKeys: Set(keys))
if resourceValues.isDirectory == true {
continue
}
let fileSize = resourceValues.totalFileSize!.description
// let fileSize = formatBytes(nbr: Int64(resourceValues.fileSize!))
let modDate = resourceValues.contentModificationDate!.formatted(.iso8601.year().month().day()
.dateSeparator(.dash)
.time(includingFractionalSeconds: false)
.timeSeparator(.omitted))
let fileData = try Data(contentsOf: fileURL)
let hashed = Insecure.MD5.hash(data: fileData)
let checksum = hashed.compactMap { String(format: "%02x", $0) }.joined()
myData.add(Record(chksum: checksum, fsize: fileSize, mdate: modDate, fpath: fileURL.path))
} catch {
print("some error occurred.")
}
}
var swiftArray = myData as! [Record]
// Reference: https://share.google/aimode/WvT8STuF70BrkFKXI
let groupedDictionary = Dictionary(grouping: swiftArray, by: { $0.chksum })
let sortedGroupedItems = groupedDictionary.sorted(by: { $0.key < $1.key })
// print out ascending checksum group item and duplicate file membership, ascending modification dates
for (key, group) in sortedGroupedItems where group.count > 1 {
print("Checksum: \(key)")
for item in group {
let fsizeVal = Int64(item.fsize) ?? 0
let bVal = formatBytes(nbr: fsizeVal)
let tpath = (item.fpath as NSString).abbreviatingWithTildeInPath
print(" \u{2022} \(bVal) \(item.mdate) \(tpath)")
}
print("\n")
}
Do not open this Swift code in Script Editor… 