{"id":48099047,"url":"https://github.com/jimschubert/ohman","last_synced_at":"2026-04-04T15:40:33.977Z","repository":{"id":329812922,"uuid":"1120726981","full_name":"jimschubert/ohman","owner":"jimschubert","description":"Find and fix files with duplicate filenames","archived":false,"fork":false,"pushed_at":"2026-01-04T00:24:30.000Z","size":41,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-09T11:52:22.427Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jimschubert.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"jimschubert","patreon":"jimschubert"}},"created_at":"2025-12-21T20:22:28.000Z","updated_at":"2026-01-04T00:24:34.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jimschubert/ohman","commit_stats":null,"previous_names":["jimschubert/ohman"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/jimschubert/ohman","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jimschubert%2Fohman","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jimschubert%2Fohman/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jimschubert%2Fohman/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jimschubert%2Fohman/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jimschubert","download_url":"https://codeload.github.com/jimschubert/ohman/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jimschubert%2Fohman/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31403960,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T10:20:44.708Z","status":"ssl_error","status_checked_at":"2026-04-04T10:20:06.846Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-04T15:40:33.489Z","updated_at":"2026-04-04T15:40:33.960Z","avatar_url":"https://github.com/jimschubert.png","language":"Go","funding_links":["https://github.com/sponsors/jimschubert","https://patreon.com/jimschubert"],"categories":[],"sub_categories":[],"readme":"# ohman\n\n[![Apache 2.0 License](https://img.shields.io/badge/License-Apache%202.0-blue)](./LICENSE)\n![Go Version](https://img.shields.io/github/go-mod/go-version/jimschubert/ohman)\n[![Go Build](https://github.com/jimschubert/ohman/actions/workflows/build.yml/badge.svg)](https://github.com/jimschubert/ohman/actions/workflows/build.yml)\n[![Go Report Card](https://goreportcard.com/badge/github.com/jimschubert/ohman)](https://goreportcard.com/report/github.com/jimschubert/ohman)\n\n\u003e Oh, Manning. You don't need to create so many duplicate files in my Dropbox.\n\u003e So many, in fact, that I need to write a tool to clean it up.\n\n## ⚠️ WARNING\n\n**THIS TOOL DELETES FILES. USE AT YOUR OWN RISK.**\n\nThis software is provided \"as is\", without warranty of any kind, express or implied. The authors are not responsible for any data loss, file corruption, or other damages resulting from the use of this tool. Always backup your files before running any delete operations, and test with `--dryrun` first.\n\n---\n\nA small CLI tool to find and optionally remove duplicate files that follow a common \"name (n).ext\" pattern (for example `book.pdf`, `book (1).pdf`). It's intended to help clean up duplicate files created by sync services, downloads, or other processes.\n\n## Features\n- Find duplicate files matching a configurable regex pattern.\n- Dry-run mode to preview matches (no filesystem changes).\n- Delete duplicates while keeping the original, or keep the newest with `--inverse`.\n- Optionally rename the kept newest duplicate back to the original filename (`--inverse-and-rename`).\n- Output results to a file via `--out` (defaults to `results.txt` when deleting and `--out` not provided).\n\n## Quick Install\n\nBuild locally with Go:\n\n```bash\ngo build -o ohman ./...\n```\n\nOr install using `go install`:\n\n```bash\ngo install ./...\n```\n\n## Usage\n\nBasic command structure:\n\n```bash\nohman [flags] \u003cpath\u003e...\n```\n\nCommon examples:\n\n- Dry-run, list duplicate files to stdout (no deletions):\n\n```bash\nohman --dryrun /path/to/search\n```\n\nExample:\n\n```shell\n$ ohman --dry-run '/Volumes/jim/Dropbox/Apps/Manning Books/Secure by Design'\nOriginal: /Volumes/jim/Dropbox/Apps/Manning Books/Secure by Design/Secure_by_Design.pdf\n  - Duplicate: /Volumes/jim/Dropbox/Apps/Manning Books/Secure by Design/Secure_by_Design (1).pdf\n  - Duplicate: /Volumes/jim/Dropbox/Apps/Manning Books/Secure by Design/Secure_by_Design (2).pdf\n```\n\n- Delete duplicates and write results to a file (relative to your current directory or use `--out`):\n\n```bash\nohman --delete --out results.txt /path/to/search\n```\n\n- Inverse deletion: keep only the newest file and delete the rest:\n\n```bash\nohman --inverse --delete /path/to/search\n```\n\n- Inverse delete + rename: keep the newest file and rename it to remove the duplicate marker (e.g. `book (2).pdf` -\u003e `book.pdf`):\n\n```bash\nohman --inverse-and-rename --delete --out kept.txt /path/to/search\n```\n\nExample:\n```shell\n$ stat -f \"%m%t%Sm %N\" * | sort -rn | cut -f2- | grep pdf\nDec 19 16:42:44 2025 The_Tao_of_Microservices (4).pdf\nApr 25 16:40:11 2020 The_Tao_of_Microservices (3).pdf\nMar  8 13:26:45 2020 The_Tao_of_Microservices (2).pdf\nMar  1 01:06:58 2020 The_Tao_of_Microservices (1).pdf\nDec 31 21:43:15 2018 The_Tao_of_Microservices.pdf\n\n$ ohman --inverse-and-rename --delete .\nResults written to results.txt\n\n$ cat results.txt \nDeleted /Volumes/jim/Dropbox/Apps/Manning Books/The Tao of Microservices/The_Tao_of_Microservices (3).pdf\nDeleted /Volumes/jim/Dropbox/Apps/Manning Books/The Tao of Microservices/The_Tao_of_Microservices (2).pdf\nDeleted /Volumes/jim/Dropbox/Apps/Manning Books/The Tao of Microservices/The_Tao_of_Microservices (1).pdf\nDeleted /Volumes/jim/Dropbox/Apps/Manning Books/The Tao of Microservices/The_Tao_of_Microservices.pdf\nRenamed /Volumes/jim/Dropbox/Apps/Manning Books/The Tao of Microservices/The_Tao_of_Microservices (4).pdf to /Volumes/jim/Dropbox/Apps/Manning Books/The Tao of Microservices/The_Tao_of_Microservices.pdf\n```\n\n## Flags\n- `--out, -o \u003cfile\u003e` — Write results to the specified file. When `--delete` is used and `--out` is omitted, `results.txt` in the current working directory is used.\n- `--regex \u003cpattern\u003e` — Custom regular expression for matching duplicate filenames. USE AT YOUR OWN RISK: a poorly chosen regex may match unintended files or cause surprising behavior; test with `--dryrun` first.\n- `--delete` — Actually delete matched duplicate files. Omit to perform a dry-run.\n- `--dryrun` — Explicit dry-run mode (prints matches only).\n- `--inverse` — When deleting, keep the newest file and delete the older/original ones instead.\n- `--inverse-and-rename` — Keep the newest and rename it to the canonical original name.\n\n## Default regex\n\n The default regex used by `ohman` looks for patterns like `name (N).ext` and matches these extensions by default:\n\n```\n(.+)\\s\\((\\d+)\\)\\.(pdf|mobi|mp4|epub|wav|mp3)$\n```\n\n(You can override this with `--regex`, but again: MODIFY THIS AT YOUR OWN RISK.)\n\n## Testing\n\nRun the unit tests:\n\n```bash\ngo test ./...\n```\n\n## Contributing\n- Please open issues or pull requests on the repository.\n- Run the tests and add new tests for bug fixes or features.\n\n## License\n- See `LICENSE` for license terms (Apache 2.0).\n\n## Acknowledgements\n\nBuilt with [Go](https://github.com/golang/go/) and [Kong](https://github.com/alecthomas/kong). Thanks to the OSS ecosystem.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjimschubert%2Fohman","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjimschubert%2Fohman","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjimschubert%2Fohman/lists"}