Redundant and duplicated files consume valuable disk capacity and complicate backup management. Because identical files often have different names or modification timestamps, simple filename comparisons are insufficient.
By computing cryptographic checksums (MD5 or SHA-256) for each file’s binary content, we can reliably group and identify duplicate files across extensive filesystem trees.
Python Duplicate File Scanner
This script traverses the target directory recursively, calculates chunked cryptographic checksums to minimize memory usage, and prompts interactively to safely delete redundant copies.
#!/usr/bin/env python3
"""
Duplicate File Finder
Author: Ahmad Mahfouz (_N1X_)
Description: Detects duplicate files across a directory tree using cryptographic hashing.
"""
import os
import sys
import hashlib
from collections import defaultdict
CHUNK_SIZE = 65536 # 64 KB read buffer
def compute_hash(filepath, algorithm="sha256"):
"""Computes the hash of a file efficiently in chunked streams."""
hasher = hashlib.new(algorithm)
try:
with open(filepath, "rb") as f:
while chunk := f.read(CHUNK_SIZE):
hasher.update(chunk)
return hasher.hexdigest()
except (PermissionError, OSError) as e:
print(f"[!] Warning: Cannot read {filepath}: {e}")
return None
def find_duplicates(target_dir):
"""Scans target_dir and groups identical files by hash."""
hashes = defaultdict(list)
total_scanned = 0
print(f"[*] Starting scan on: {os.path.abspath(target_dir)}")
for root, _, files in os.walk(target_dir):
for filename in files:
filepath = os.path.join(root, filename)
# Skip symlinks to avoid duplicate loops
if os.path.islink(filepath):
continue
file_hash = compute_hash(filepath)
if file_hash:
hashes[file_hash].append(filepath)
total_scanned += 1
if total_scanned % 100 == 0:
print(f"\r[*] Processed {total_scanned} files...", end="", flush=True)
print(f"\r[*] Completed scan. Total files analyzed: {total_scanned}\n")
return {h: paths for h, paths in hashes.items() if len(paths) > 1}
def review_and_clean(duplicates):
"""Prompts the user interactively to inspect and remove duplicates."""
if not duplicates:
print("[+] No duplicate files detected.")
return
print(f"[+] Found {len(duplicates)} sets of duplicate files:\n")
for file_hash, paths in duplicates.items():
print(f"Checksum: {file_hash}")
for idx, path in enumerate(paths, 1):
size_kb = os.path.getsize(path) / 1024
print(f" [{idx}] {path} ({size_kb:.2f} KB)")
confirm = input("Enter file numbers to delete separated by commas (or press Enter to skip): ").strip()
if confirm:
choices = [int(c.strip()) for c in confirm.split(",") if c.strip().isdigit()]
for choice in choices:
if 1 <= choice <= len(paths):
target_to_remove = paths[choice - 1]
try:
os.remove(target_to_remove)
print(f" [-] Removed: {target_to_remove}")
except OSError as e:
print(f" [!] Failed to remove {target_to_remove}: {e}")
print("-" * 50)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python find_duplicates.py <directory_path>")
sys.exit(1)
target_directory = sys.argv[1]
if not os.path.isdir(target_directory):
print(f"Error: Directory '{target_directory}' does not exist.")
sys.exit(1)
dup_map = find_duplicates(target_directory)
review_and_clean(dup_map)