Skip to main content

rtrash

rtrash logo

Native FreeDesktop trash — rm-compatible put, full trash-cli suite, Python bindings.
One multi-call binary · recoverable by design · parallel empty

crates.io PyPI docs

Terminal demo: rtrash put → list → restore → empty in a pinned sandbox trash

put → list/status → restore → empty · asciicast · docs site

$ cargo binstall rtrash && rtrash setup
$ rtrash notes.txt                 # put (also: rtrash -rf dir, multi-call trash-put)
$ rtrash list && rtrash status
$ rtrash restore                   # TUI on a TTY; --plain for scripts
$ rtrash empty --plain
More of the surface (rm-flags, multi-call, trash-rm, dry-run, keys)

Terminal demo: rtrash -rf, multi-call, rm globs, empty --dry-run, keys

Feature Demo
FreeDesktop put / list / restore / empty quickstart GIF above
rtrash -rf rm-shaped put suite GIF
Multi-call trash-put / trash-list / … suite GIF
rtrash rm / trash-rm globs suite GIF
empty --dry-run suite GIF
Custom keys (rtrash keys, keys.toml) suite GIF
Interactive TUI (fuzzy, multi-select, ?) TTY — not in the GIF; run rtrash restore
Python unlink / rmtree bindings

Demos are regenerable: ./docs/demo/record.sh (pinned --trash-dir sandbox — safe). Source: docs/demo/.

Documentation

Doc Contents
docs/getting-started.md Install CLI + Python, shortest paths
docs/demo/ Terminal demo cast/GIF + recorder
docs/architecture.md FreeDesktop layout, safety vs rm / os.remove / trash-cli
docs/benchmarks.md Measured safer/better/faster vs trash-cli
docs/bindings.md Maturin/PyO3 API (unlink / rmtree replacements)
CHANGELOG.md Release notes (towncrier fragments in docs/newsfragments/)

Site: https://rtrash.rgoswami.me · Org sources: docs/orgmode/.

Python: replace permanent delete

import rtrash
rtrash.unlink("file.txt")    # not os.remove — GIL released during I/O
rtrash.rmtree("build/")      # not shutil.rmtree
$ pip install rtrash

Platform

OS Backend Notes
Linux FreeDesktop home + per-mount trash Primary niche; multi-call setup, musl releases, full suite tests
macOS FreeDesktop home trash only (experimental) $XDG_DATA_HOME/Trash or ~/.local/share/Trash. Not Finder / system Trash
Windows System Recycle Bin (shell APIs) put/list/restore/empty/rm/status. Not FreeDesktop on-disk layout. Multi-call setup is Unix-oriented

On Linux/macOS TTY, bare rtrash restore opens the interactive restore browser (ratatui). Windows uses numbered restore selection (no TUI).

Install

Registry (primary): published on crates.io/crates/rtrash and pypi.org/project/rtrash.

$ cargo install rtrash          # CLI from crates.io (needs Rust toolchain)
$ cargo binstall rtrash         # CLI prebuilt musl (x86_64 / aarch64 Linux)
$ pip install rtrash            # Python bindings (Linux x86_64 wheels, CPython 3.10–3.14)
$ rtrash setup                  # multi-call links, completions, man under ~/.local

Always run rtrash setup after the CLI binary lands on PATH (override with --prefix=DIR).

1. cargo binstall (preferred binary install)

Uses cargo-binstall to download the prebuilt Linux musl static tarball for your CPU (no local compile). GitHub Releases ship x86_64 and aarch64 musl assets for tag v*. Crate metadata remaps typical glibc hosts (*-unknown-linux-gnu) to the matching musl tarball, so a normal:

$ cargo binstall rtrash
$ rtrash setup

does not look for a non-existent *-linux-gnu*.tar.gz on x86_64 or aarch64 Linux.

Arch Asset basename
x86_64 rtrash-<version>-x86_64-unknown-linux-musl.tar.gz
aarch64 (ARM64) rtrash-<version>-aarch64-unknown-linux-musl.tar.gz

Naming matches [package.metadata.binstall] in Cargo.toml / scripts/package-release.sh.

Prebuilt musl tarballs are Linux-only. macOS (experimental FreeDesktop home trash) and Windows (Recycle Bin backend) are from-source (cargo install rtrash); there are no Finder Trash or FreeDesktop-as-Recycle-Bin assets.

2. Manual musl tarball (no cargo at all)

When a Release exists, download the matching asset from the repository Releases page, extract, put bin/ on PATH, then rtrash setup --force (or follow INSTALL.txt in the tarball).

Build the same artifact yourself (builder host with musl target):

$ ./scripts/package-release.sh x86_64-unknown-linux-musl
$ ./scripts/package-release.sh aarch64-unknown-linux-musl
# or, on a host that can build both:
$ ./scripts/package-release.sh --all
#  dist/rtrash-<version>-{x86_64,aarch64}-unknown-linux-musl.tar.gz

CI: .github/workflows/release-musl.yml builds both targets on v* tags (x86_64 and ubuntu-24.04-arm) and attaches the tarballs to the GitHub Release (what binstall downloads).

3. From crates.io (cargo install)

Requires a Rust toolchain (MSRV 1.77). Dynamically linked glibc binary by default (compiles on the install host).

$ cargo install rtrash
$ rtrash setup

Tip of main without waiting for a release:

$ cargo install --git https://github.com/HaoZeke/rtrash
$ rtrash setup

4. Python (pip install rtrash)

Prebuilt manylinux wheels for CPython 3.10–3.14 on Linux x86_64 (FreeDesktop only).

$ pip install rtrash
$ python -c "import rtrash; print(rtrash.version())"

From a checkout (dev): pip install maturin && maturin develop --features python.

What rtrash setup installs

What Where (default)
Multi-call symlinks (trash-put, trash-empty, trash-list, trash-restore, trash-rm, trash) ~/.local/bin/ → this rtrash
bash completion ~/.local/share/bash-completion/completions/rtrash (+ multi-call links)
zsh completion ~/.local/share/zsh/site-functions/_rtrash
fish completion ~/.config/fish/completions/rtrash.fish (+ multi-call *.fish links)
man page ~/.local/share/man/man1/rtrash.1
Midnight Commander menu sample (optional) ~/.local/share/rtrash/mc.menu.sample — append into ~/.config/mc/menu by hand; setup never overwrites your menu

Useful flags: rtrash setup --dry-run, --force (refresh after upgrade), --with-rm (also link rm → put into trash), --prefix=/usr/local.

Subcommands work without multi-call names: rtrash put|empty|list|status|restore|rm ….

Multi-call name Meaning
rm / trash / trash-put put (move paths into the trash)
trash-list list
trash-restore restore
trash-empty empty
trash-rm permanently delete matching trash entries

Subcommand rtrash rm PATTERN is the same as multi-call trash-rm (not the same as multi-call rm, which puts).

Packagers and custom layouts

Sources also live in-tree for packaging (completions/, man/rtrash.1). From any installed binary:

$ rtrash completions bash > rtrash.bash
$ rtrash completions zsh  > _rtrash
$ rtrash completions fish > rtrash.fish
$ rtrash man              > rtrash.1
$ rtrash setup --prefix=/usr --force    # system prefix when packaging

Contributing and releases

  • Changelog: add a towncrier fragment under docs/newsfragments/ for user-visible changes:
    • towncrier create -c "Short description." +slug.added.md (or .fixed.md, .changed.md, …)
    • At release: towncrier build --version X.Y.Z --yes then tag vX.Y.Z
  • Version lockstep: cocogitto (cog.toml) bumps Cargo.toml, pyproject.toml, and docs/source/conf.py together (disable_changelog = true; towncrier owns CHANGELOG.md).
  • Hooks: prek install then prek run -a (builtin large-file check, yaml/toml, codespell, version lockstep). CI also runs HaoZeke/large-file-auditor and lychee.
  • Docs PRs: the documentation workflow uploads a documentation artifact; HaoZeke/doc-previewer comments a preview link on the PR.

Tutorial

Trash a file, inspect the trash, restore it, then empty:

$ echo data > scratch.txt
$ rtrash scratch.txt
$ rtrash list
2026-07-03 14:00:00 /home/you/scratch.txt
$ rtrash restore scratch.txt
restored '/home/you/scratch.txt'
$ rtrash scratch.txt
$ rtrash empty
Removed 1 item

Selective permanent delete from the trash (trash-cli trash-rm):

$ rtrash put a.o b.c
$ rtrash rm '*.o'          # quote globs; removes a.o from trash only
$ rtrash list              # b.c still listed

rtrash FILE with no subcommand behaves like rm, so shell habits carry over: rtrash -rf build/ moves build/ to the trash instead of unlinking it.

Scripts / tests: pin with --trash-dir=… and/or isolate XDG_DATA_HOME so list/empty/restore/rm do not walk every mount’s trash.

Reference

rtrash put (also rm, trash, trash-put, or bare rtrash)

Accepts the common rm(1) flags with the same semantics, except that files move to the trash: -f, -i, -I, --interactive[=WHEN], -r/-R, -d, -v, --one-file-system (accepted for rm compatibility; no-op because entries move whole and are never walked like rm -x), --preserve-root (default), --no-preserve-root, --. Directories need -r (or -d when empty), ./..// are refused (unless --no-preserve-root for /), and exit codes mirror rm (0 success, 1 failure, 2 usage error).

As with GNU rm, the last of -f / -i / -I (and the matching long forms) wins for prompt vs force behavior. -f also ignores missing paths.

Trash placement follows the spec: the home trash ($XDG_DATA_HOME/Trash, defaulting under ~/.local/share) for same-filesystem files, $top/.Trash/$uid (must be a sticky non-symlink directory) or $top/.Trash-$uid on other mounts, with a copy-into-home-trash fallback when the volume cannot host a trash directory. Names are reserved atomically (create-new / O_EXCL on the .trashinfo), so concurrent invocations never clobber each other; collisions get name.2, name.3, ...

Putting a directory updates that trash dir’s FreeDesktop directorysizes cache (size mtime percent-encoded-name). Putting ordinary files does not add directory lines.

rtrash empty [DAYS] (also trash-empty)

Purges every trash directory visible to the user (home trash plus mounted volumes). With DAYS, only items trashed more than DAYS days ago go. Entries are removed in parallel. Orphaned files/ entries (no .trashinfo) and entries with broken metadata are purged on a full empty, and the directorysizes cache is pruned when present. Options: -n/--dry-run (also prints an approximate reclaimable size via a fast in-process walk of the victims, like a small du of what would go away), -v/--verbose, --trash-dir=PATH (repeatable), -f (accepted for trash-cli compatibility; emptying never prompts).

rtrash list (also trash-list)

Prints DELETION-DATE ORIGINAL-PATH per item, oldest first, in the trash-list output format (YYYY-MM-DD HH:MM:SS plus the original path). Scans the home trash and per-mount trash directories owned by the current user, or only the directories given with --trash-dir=PATH (repeatable).

Flag Meaning
--json JSON array of {deletion_date, original, name, trash_dir}
--older-than=DAYS Only items trashed more than DAYS days ago (same cutoff as empty DAYS)
--newer-than=DAYS Only items trashed within the last DAYS days
--home-only Home trash only

rtrash status

Per-root item count and reclaimable size (uses directorysizes when valid). Same discovery pins as list. Also accepts --json, --older-than=DAYS, --newer-than=DAYS.

rtrash restore [PATH] (also trash-restore)

Invocation Behavior
rtrash restore PATH Restore the item whose original path was PATH (exact match)
rtrash restore TUI (TTY): fuzzy filter, multi-select (Space/a/A), bulk restore; session until q
rtrash restore --plain Numbered line list + index (scripts / no TUI)
printf '0\n' | rtrash restore Piped index without TUI
rtrash restore --cwd-only Same as bare restore, only originals under $PWD
Single match Restores immediately without prompting

TUI keys (shared by restore / empty / put): defaults ↑↓/jk move · PgUp/PgDn page · g/G first/last · Space mark · a/A all/clear · / live fuzzy · ? help · Enter action · q/Esc quit.

Fully customizable: every action can be remapped in $XDG_CONFIG_HOME/rtrash/keys.toml (or RTRASH_KEYS=path). TOML under [keys]; rtrash keys --list shows the resolved map; rtrash keys --sample prints a full template. Help (?) reflects the live map.

Browser-specific actions (also remappable): restore toggle_force · empty toggle_dry_run · put toggle_recursive / toggle_force.

TUI empty: bare rtrash empty on a TTY opens the multi-select permanent-delete browser (--plain or non-TTY keeps classic empty).

TUI put: bare rtrash put on a TTY opens the multi-select path browser for the current directory (--plain or FILE operands keep classic put).

Existing paths at the destination are preserved unless -f / --force is given; with -f, a blocking destination is removed first. Same-filesystem restore uses rename; cross-device restore copies then deletes the trash payload (needed when put fell back to the home trash). Options: --trash-dir=PATH (repeatable), --home-only, --cwd-only.

rtrash rm PATTERN... (also trash-rm)

Permanently deletes trash entries whose original path, basename, or trash name matches a shell-style glob PATTERN (quote globs from the shell). Matching files/ payloads and .trashinfo files are removed; non-matches stay. Does not restore. Options: -v/--verbose, --trash-dir=PATH (repeatable).

FreeDesktop durability notes

Previously deferred items are implemented in the shipped put/empty path:

  • Durable .trashinfo: put fsyncs the reserved info file (and best-effort the info/ dir) before moving the payload.
  • EXDEV fidelity: cross-device put/restore copies preserve content, symlink-as-link, mode, and mtime (not a bare content-only copy).
  • Put/empty exclusion: each trash root takes an exclusive flock on .rtrash.lock for put and empty so the pair cannot tear mid-operation.
  • Btrfs multi-subvol topdir: volume topdir is the longest mount-point prefix from /proc/self/mounts, not a pure st_dev parent walk.
  • Default multi-volume empty: with no --trash-dir, empty/list/restore/rm discover home trash plus existing user trash on every non-pseudo mount (including /), matching trash-cli’s multi-volume default. Pin with --trash-dir in scripts.

Limitations

  • Platform: Linux FreeDesktop is the primary niche. macOS is experimental FreeDesktop home trash (not Finder). Windows uses the system Recycle Bin (not FreeDesktop layout).
  • Restore UI: bare rtrash restore on a TTY opens the ratatui restore browser (filter, navigate, multi-restore). Use --plain or a piped index for non-interactive selection. Scripts and path restore stay first-class.
  • Not a general soft-delete database: only the FreeDesktop on-disk layout.
  • EXDEV does not re-create xattrs/ACLs/hardlinks (mode+mtime+symlink+bytes only).
  • Locks are local flock (advisory on some network FS).

Performance

Historical single-machine comparison against trash-cli 0.24.5.26 (CPython 3) on an NVMe-backed Linux host, best of warm runs. These numbers are not continuously re-verified in CI and will vary by filesystem, core count, and trash layout; treat them as order-of-magnitude evidence that native startup and parallel empty help, not as a live leaderboard.

Operation trash-cli rtrash ratio
empty, 100 000 entries 0.92 s 0.36 s 2.5x
empty, 20 000 entries 0.21 s 0.07 s 3x
put, one file 52 ms 1 ms 52x

Full empty was further optimized for large trashcans: no pre-scan when not verbose, d_type-fast unlinkat for regular files, and serial wipes of the files/ then info/ roots with parallel children inside each. Emptying uses an in-process bulk tree delete (readdir/unlinkat walk inspired by empty-source rsync --delete, not a shell-out to rsync). On btrfs, if a trash payload is a real subvolume root, empty uses BTRFS_IOC_SNAP_DESTROY instead of walking the tree. Interactive put mainly wins on process startup versus trash-cli.

Development

$ cargo test

Integration tests isolate trash under a temporary XDG_DATA_HOME and pin empty / list / restore / rm with --trash-dir=… so they never clear the host trash.

Citation

@software{rtrash,
  author = {Goswami, Rohit},
  title  = {rtrash: a fast rm-compatible freedesktop.org trash tool},
  url    = {https://github.com/HaoZeke/rtrash},
  year   = {2026}
}

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rtrash-0.1.4.tar.gz (1.8 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

rtrash-0.1.4-cp314-cp314-manylinux_2_39_x86_64.whl (634.0 kB view details)

Uploaded CPython 3.14manylinux: glibc 2.39+ x86-64

rtrash-0.1.4-cp313-cp313-manylinux_2_39_x86_64.whl (636.2 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.39+ x86-64

rtrash-0.1.4-cp312-cp312-manylinux_2_39_x86_64.whl (636.1 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.39+ x86-64

rtrash-0.1.4-cp311-cp311-manylinux_2_39_x86_64.whl (634.0 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.39+ x86-64

rtrash-0.1.4-cp310-cp310-manylinux_2_39_x86_64.whl (634.3 kB view details)

Uploaded CPython 3.10manylinux: glibc 2.39+ x86-64

File details

Details for the file rtrash-0.1.4.tar.gz.

File metadata

  • Download URL: rtrash-0.1.4.tar.gz
  • Upload date:
  • Size: 1.8 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: maturin/1.14.1

File hashes

Hashes for rtrash-0.1.4.tar.gz
Algorithm Hash digest
SHA256 9e733b6cb106c654e0807a6ff321b83ac60e5f3a3d1d02f587b9cd8ff641e35f
MD5 fa8110407f8231956cc603f49fe83f78
BLAKE2b-256 8db7c1d79a4e89c345e0a3c5429ddad591ef2c7ef9d8940fca1c2322158e7192

See more details on using hashes here.

File details

Details for the file rtrash-0.1.4-cp314-cp314-manylinux_2_39_x86_64.whl.

File metadata

File hashes

Hashes for rtrash-0.1.4-cp314-cp314-manylinux_2_39_x86_64.whl
Algorithm Hash digest
SHA256 4647b0e3c5beff4c26d4cb9ebb25f6f3659465005c03cc6a686cb81a5269a8f9
MD5 c664fd9b61938ff1d8fcb1769ddd5161
BLAKE2b-256 57fef50f5b4335d4a50d886f3ec854f1b1b23446cd61906f4fb109aaa49c7a5d

See more details on using hashes here.

File details

Details for the file rtrash-0.1.4-cp313-cp313-manylinux_2_39_x86_64.whl.

File metadata

File hashes

Hashes for rtrash-0.1.4-cp313-cp313-manylinux_2_39_x86_64.whl
Algorithm Hash digest
SHA256 eecf4ddcb7f53920a5bf14da40f29273b03498573aa68c9df1b4d95f1065cbf8
MD5 34512285584145039533834840da1a8d
BLAKE2b-256 cdba1bd05972486e0f1d3089b74c8b610e586890ef5b77b1c44e6c7827ad370f

See more details on using hashes here.

File details

Details for the file rtrash-0.1.4-cp312-cp312-manylinux_2_39_x86_64.whl.

File metadata

File hashes

Hashes for rtrash-0.1.4-cp312-cp312-manylinux_2_39_x86_64.whl
Algorithm Hash digest
SHA256 23b154020f76c7b329e8c47454e7554543a482f1407be1defc8f038824f0d559
MD5 30d7cb1adc139ceca5515f0d1abaa10c
BLAKE2b-256 725382257c9c8df5426b71150ad20b5b74522564754380b7fd6fcccfb8726d89

See more details on using hashes here.

File details

Details for the file rtrash-0.1.4-cp311-cp311-manylinux_2_39_x86_64.whl.

File metadata

File hashes

Hashes for rtrash-0.1.4-cp311-cp311-manylinux_2_39_x86_64.whl
Algorithm Hash digest
SHA256 795b3a411f7ea22c3e7b4bc65f49093b7a6ec956cad5de032a2e024507470aab
MD5 42c025483d2db876aac8259672121741
BLAKE2b-256 8ff77919d49069cc4ed05e7646a23ab04b639d366132e698c2bb7b0c5a4d8f78

See more details on using hashes here.

File details

Details for the file rtrash-0.1.4-cp310-cp310-manylinux_2_39_x86_64.whl.

File metadata

File hashes

Hashes for rtrash-0.1.4-cp310-cp310-manylinux_2_39_x86_64.whl
Algorithm Hash digest
SHA256 ab1a1d0eda450619a7492fe0c787ef63e259df32a2866c7220c68b9e50f9a2b1
MD5 1a340552f07ebb908cc50b0945fcb6a1
BLAKE2b-256 00135448a745fa208d90e8f9cb7af07b7a8302ad69c850c7967f462ea0f7c3ca

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.4 This release

6 files

0.1.3

6 files

0.1.2

5 files

0.1.1

5 files

0.1.0

5 files

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