Skip to main content

duplicates

Scan for identical files (duplicates) in subdirectories.

Requirements

  • Python >= 3.11
  • POSIX (Linux, macOS); MS Windows is not supported.

Installation

$ uv tool install duplicates

Or, if you prefer pipx:

$ pipx install duplicates

Description

To find files with identical content, the given directories are scanned and files of the same size have their SHA-256 fingerprints compared. Two files with identical fingerprints are considered to have the same content. There is a tiny chance for two files with the same fingerprint to have different content, but that chance is very remote.

Large files (≥ 64 KiB) are first compared by a cheap "partial" SHA-256 over their first and last 4 KiB; only files that survive that prefilter are read in full. For collections of large near-duplicates (videos, archives) this avoids reading most of the data.

Symbolic links and hidden entries are ignored by default. This behavior can be changed with the CLI options --follow / --hidden or the constructor options ignore_symlinks / ignore_hidden.

CLI examples

Print a short command overview:

$ duplicates --help

Scan directories dirA, dirB and dirC and report identical files:

$ duplicates dirA dirB dirC

dirA/file01
        dirA/file01.bak
        dirB/file.bak
dirA/file02
        dirB/file02~

The oldest file is printed without indent; identical files are listed indented by a tab. The oldest file is treated as the original.

If you are willing to take risks, you can delete all duplicates at once. I wouldn't dare, but you get the picture:

$ duplicates --dups-only dirA dirB | while read dups ; do xargs -0 rm $dups ; done

With --dups-only, all duplicates for one original are printed on a single line separated by \0 (ASCII NUL).

For the fish shell the syntax is almost identical:

$ duplicates --dups-only dirA dirB | while read -la dups ; xargs -0 rm $dups ; end

JSON output

For scripted consumption, --json emits the full result on stdout including a statistics block with counts and the scan's elapsed time:

$ duplicates --json dirA dirB
{
  "scanned_paths": ["dirA", "dirB"],
  "duplicates": [
    {
      "hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
      "size": 1234,
      "files": [
        {"path": "dirA/file01", "age": 1700000000.0},
        {"path": "dirA/file01.bak", "age": 1710000000.0}
      ]
    }
  ],
  "statistics": {
    "total_files": 12,
    "unique_files": 10,
    "duplicate_groups": 1,
    "duplicate_copies": 1,
    "duplicate_bytes": 1234,
    "unreadable_files": 0,
    "elapsed_seconds": 0.0123
  }
}

--json is mutually exclusive with --dups-only and --summary. Combine with --unique to also include the unique files in the output.

Progress on long scans

--verbose surfaces phase markers and per-file logs to stderr — useful when running over a slow filesystem (SMB, large library) where the tool might otherwise look stuck:

INFO: Scanning 1 path(s)...
INFO: Scanned 1284 file(s) so far...
INFO: Discovered 4012 file(s) in 3987 size group(s)
INFO: Partial-hashing 12 file(s)...
INFO: Partial-hashing /films/movie.mp4 (5.2 GiB)
INFO: Full-hashing 4 file(s)...
INFO: Full-hashing /films/movie.mp4 (5.2 GiB)

--debug adds per-directory and per-ignored-entry messages on top.

Python API

from duplicates import DupFinder

uniq, dups, unreadable = DupFinder().scan(".")

uniq is a list of unique FileEntry objects. dups is a list of duplicate groups, where each group is a list of FileEntry objects with identical content. Use entry.age to identify the oldest file in a group. unreadable collects files that could not be fingerprinted (permission denied, I/O error); they cannot be classified and are returned separately instead of being silently dropped.

A FileEntry is a dataclass with the following fields:

  • path: a pathlib.Path
  • size: file size in bytes
  • age: modification time in seconds (Unix time)
  • hash: the SHA-256 fingerprint (None for unique files where no hash was needed)

Progress messages are emitted via the logging module on the duplicates logger; configure logging in your application to see them.

Development

$ uv sync
$ uv run pytest
$ uv run ruff check .
$ uv run basedpyright

Metadata

Release files for duplicates 0.4.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for duplicates 0.4.3
File Size Uploaded Provenance
duplicates-0.4.3.tar.gz 9.2 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for duplicates 0.4.3
File Interpreter ABI Platform
duplicates-0.4.3-py3-none-any.whl Python 3 none any Details

Total release size: 19.9 kB

Release files / duplicates-0.4.3.tar.gz

Download URL duplicates-0.4.3.tar.gz
Size 9.2 kB
Tags Source
SHA-256 checksum
How to use checksums
041a3ee97761e3c529bce3bce5f3edb862ee76c8bb1a524328499bff8d671f2d
BLAKE2b-256 checksum
How to use checksums
1878b00af6eecb4a0f22bf8e9f8749ae247802dd1fd86e28898b348e560e90f1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / duplicates-0.4.3-py3-none-any.whl

Download URL duplicates-0.4.3-py3-none-any.whl
Size 10.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
545e722ff8712bb217b7b609bfc3f1b3e00aec28c31987cdce325feee08807bc
BLAKE2b-256 checksum
How to use checksums
7b85796f1ead3847b68b604e4779a6ac01dd2a82456b989b2d66cfecf738436c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for duplicates 0.4.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page