duplo - созвучно с «дупло» (укромное место, где что-то спрятано) и английским duplicate.
CLI-утилита для поиска дубликатов файлов и каталогов с возможностью удаления.
Документ соответствует ревизии кода на 2026-09-25.
Проект в разработке. Режимы удаления (--interactive, --auto-first)
в текущей ревизии небезопасны. Перед любым удалением сделайте резервную
копию данных.
Известные дефекты, которые могут привести к потере данных:
--find-identical-dirsсравнивает каталоги только по файлам, у которых есть дубликат. Уникальные файлы и подкаталоги при сравнении не учитываются. Каталог, признанный «идентичным», удаляется целиком (shutil.rmtree) вместе с уникальным содержимым.- Выбор по файлам и выбор по каталогам не согласуются между собой. Комбинация «оставить первый файл» и «оставить последний каталог» может удалить все копии.
- В ручном режиме (
[m]) пустой ввод, ввод через запятую или номер вне диапазона приводят к удалению всех копий группы. - Symlink на файл считается дубликатом своей цели. Удалённым может оказаться оригинал, а сохранённой — ссылка.
- Все пустые файлы (
__init__.py,.gitkeep,.nomedia) считаются дубликатами друг друга. - Перед удалением файл не сверяется повторно: решение принимается по MD5 и по кэшу (путь, размер, mtime).
--dry-runне показывает содержимое подкаталогов, которые будут удалены. Реальный объём удаления может быть больше показанного.
Прочие ограничения:
--workersпринимается, но не используется: хеширование выполняется в одном потоке.- Кэш хешей хранится в формате
pickleв текущем каталоге. Загрузка чужого или подменённого файла кэша приводит к выполнению произвольного кода. Не запускайте утилиту из каталогов, доступных на запись другим пользователям, и не используйте кэш-файлы из ненадёжных источников. - Hardlink'и считаются дубликатами. Их удаление не освобождает место, но учитывается в отчёте как освобождённое.
- Код возврата всегда 0, в том числе при несуществующем каталоге и при ошибках удаления. Для использования в скриптах результат нужно проверять по выводу.
- На Linux и macOS FIFO в сканируемом дереве приводит к зависанию.
- На Windows файлы с атрибутом read-only не удаляются. Удаление каталога с таким файлом прерывается на середине, и каталог остаётся удалённым частично.
Безопасный режим использования на текущей ревизии — только анализ, без флагов удаления.
- Поиск дубликатов файлов: группировка по размеру, затем сравнение MD5.
- Статистика по категориям, расширениям и каталогам.
- Поиск каталогов с совпадающим набором дублирующихся файлов (см. ограничения выше).
- Классификация файлов по расширению: images, videos, audio, documents, archives, executables, scripts, data, system, fonts, design, other.
- Кэш хешей для повторных запусков.
- Интерактивный и автоматический выбор копий для удаления, режим
--dry-run.
- Python 3.7 или новее. Python 3.7 и 3.8 больше не получают обновлений безопасности, рекомендуется 3.9+.
- Внешних зависимостей нет, используется только стандартная библиотека.
- Права на чтение сканируемого каталога. Для удаления нужны права на запись.
git clone https://github.com/it255ru/duplo.git
cd duploВиртуальное окружение не обязательно, так как зависимостей нет.
Анализ каталога без удаления:
python main.py /path/to/dirГруппировка дубликатов по категориям:
python main.py /path/to/dir --group-by-categoryПоиск совпадающих каталогов (только отчёт):
python main.py /path/to/dir --find-identical-dirsПросмотр плана удаления без выполнения:
python main.py /path/to/dir --interactive --dry-runИнтерактивное удаление (см. раздел «Статус и известные ограничения»):
python main.py /path/to/dir --interactiveАвтоматическое удаление всех копий, кроме первой. «Первая» определяется порядком обхода файловой системы, а не алфавитом или датой. На FAT32 и exFAT это порядок создания записей в каталоге.
python main.py /path/to/dir --auto-firstПеред фактическим удалением утилита выводит план и запрашивает
подтверждение (y/n).
| Аргумент | Описание | По умолчанию |
|---|---|---|
source_dir |
Каталог для анализа | обязательный |
--workers N |
Зарезервирован, в текущей ревизии не используется | 8 |
--cache-file PATH |
Файл кэша хешей (pickle) |
hash_cache.pkl в текущем каталоге |
--group-by-category |
Выводить дубликаты по категориям | выключено |
--find-identical-dirs |
Искать совпадающие каталоги | выключено |
--interactive |
Интерактивный выбор копий для удаления | выключено |
--auto-first |
Оставить первую копию в каждой группе | выключено |
--dry-run |
Показать план удаления без выполнения | выключено |
- Рекурсивный обход
source_dirчерезos.walk. Для каждого файла фиксируются путь, размер и mtime. Каталоги и файлы, недоступные для чтения, пропускаются без сообщения. - Группировка по размеру. Файлы с уникальным размером дальше не обрабатываются.
- Для кандидатов вычисляется MD5. Если в кэше есть запись с тем же путём, размером и mtime, хеш берётся из кэша.
- Группы с одинаковым MD5 считаются дубликатами. Побайтовое сравнение не выполняется.
- С
--find-identical-dirsдля каждого каталога строится отсортированный список хешей его файлов-дубликатов. Каталоги с одинаковым списком объединяются в группу.
============================================================
СВОДНАЯ СТАТИСТИКА
============================================================
Общее количество файлов: 21603
Общий объем данных: 113.97 GB
Группа 1 (Хеш: 637bfe87...), Размер: 2.20 MB, Категория: images
-> X:\photos\CIMG2033.JPG
-> X:\photos\Копия CIMG2033.JPG
Группа идентичных каталогов #1:
[1] X:\photos\a\2014-01 (5 файлов, 6.02 MB)
[2] X:\photos\b\2014-01 (5 файлов, 6.02 MB)
Количество файлов и размер в группе каталогов относятся только к файлам, лежащим непосредственно в каталоге, без подкаталогов. Совпадение этих чисел не означает совпадения содержимого.
duplo/
├── main.py
├── README.md
└── LICENSE
Файл кэша (hash_cache.pkl или путь из --cache-file) создаётся при
запуске и не должен попадать в репозиторий. Добавьте его в .gitignore.
MIT, см. LICENSE.
Issues и pull requests принимаются через GitHub. К исправлениям, которые касаются удаления данных, прикладывайте тест, воспроизводящий проблему на текущей ревизии.