Skip to main content

Python bindings for rtrash — FreeDesktop trash as a safe alternative to os.remove

Project description

rtrash

rtrash logo

Native Rust FreeDesktop trash for Linux: an rm-compatible put path and the everyday trash-cli suite (trash-put, trash-empty, trash-list, trash-restore, trash-rm) as one multi-call binary. Implements the freedesktop.org Trash specification. No interpreter startup; emptying deletes entries in parallel via rayon.

Documentation

Doc Contents
docs/getting-started.md Install CLI + Python, shortest paths
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/)

Org-mode sources (readcon-core style): docs/orgmode/.

Python: replace permanent delete

import rtrash
rtrash.unlink("file.txt")    # not os.remove
rtrash.rmtree("build/")      # not shutil.rmtree
$ pip install rtrash

Docs site

Published at https://rtrash.rgoswami.me (GitHub Pages).

Project documentation is authored in orgmode under docs/orgmode/ and built to a Sphinx + Shibuya static site (readcon-style: org → RST → HTML).

# needs: pandoc (or emacs + ox-rst), python3, venv
./docs/build.sh
# HTML → docs/build/index.html
python3 -m http.server -d docs/build 8000
Page Source
Landing docs/orgmode/index.org
Getting started docs/orgmode/getting-started.org
Architecture docs/orgmode/architecture.org
Benchmarks docs/orgmode/benchmarks.org
Python bindings docs/orgmode/bindings.org

Markdown mirrors under docs/*.md remain for quick GitHub reading; the site is the structured presentation of the same material.

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

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 status

Prints per-trash-root and total item count plus approximate reclaimable size (same disk-usage walk as empty dry-run). Accepts --home-only and --trash-dir.

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).

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

Project details


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.3.tar.gz (149.6 kB 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.3-cp314-cp314-manylinux_2_39_x86_64.whl (633.6 kB view details)

Uploaded CPython 3.14manylinux: glibc 2.39+ x86-64

rtrash-0.1.3-cp313-cp313-manylinux_2_39_x86_64.whl (635.7 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.39+ x86-64

rtrash-0.1.3-cp312-cp312-manylinux_2_39_x86_64.whl (635.6 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.39+ x86-64

rtrash-0.1.3-cp311-cp311-manylinux_2_39_x86_64.whl (633.5 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.39+ x86-64

rtrash-0.1.3-cp310-cp310-manylinux_2_39_x86_64.whl (633.8 kB view details)

Uploaded CPython 3.10manylinux: glibc 2.39+ x86-64

File details

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

File metadata

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

File hashes

Hashes for rtrash-0.1.3.tar.gz
Algorithm Hash digest
SHA256 c1f1687cfc16c670f2d3aeb03e0eb4c219e71ce4a6288a4567339f5244b87c58
MD5 ae2915eb590a6fac318126eb58a94ca2
BLAKE2b-256 c6919f645e2b522bf9ced00803d2d2a731bcafad45d60f00af8970d68dd3431b

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rtrash-0.1.3-cp314-cp314-manylinux_2_39_x86_64.whl
Algorithm Hash digest
SHA256 b7a7ce8afc2ddd791448ead0fbfa2b80cb0cf26d3f0088bd1bf7d8854be505ba
MD5 b33e2760e3b2929373017af617952528
BLAKE2b-256 bacbe120742f2821ffcd87537cb091d4ac69ced8b6c3af2ebcd1bf91a4d34252

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rtrash-0.1.3-cp313-cp313-manylinux_2_39_x86_64.whl
Algorithm Hash digest
SHA256 93bb4bd7ba0c632f069bbc99741863f0a331e4f39bcce8c3053333a4a2b146a0
MD5 24f6f7209de82714fe176dc4541eab73
BLAKE2b-256 a5e2506cf0cd24f2a99a2ea90a7425b34c76eb334e9ae3d74b1d9bc11a9b419b

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rtrash-0.1.3-cp312-cp312-manylinux_2_39_x86_64.whl
Algorithm Hash digest
SHA256 281e98ca794b5d707847e26a36e610f821081ce7d83a49c9de04be24d828ec6f
MD5 e08a7a2c006867319303ee108a1ceadf
BLAKE2b-256 1bc7408f6ec486c540964f966104702fa1d083fa594864e168a2dbafaa6752a8

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rtrash-0.1.3-cp311-cp311-manylinux_2_39_x86_64.whl
Algorithm Hash digest
SHA256 8342a3d142f5dfc247e2d18df70ccf1116af9309df9d40ad7344666dc6965954
MD5 37cfb7bbf2ae51b7708562134135c8ac
BLAKE2b-256 60e34ee0701f439ba1ad5c7e64ab615fdd49f584c8351b260724e796726ec1ce

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rtrash-0.1.3-cp310-cp310-manylinux_2_39_x86_64.whl
Algorithm Hash digest
SHA256 86addcf38e5520d5d231bb08ed2176b83778158a85166eedb30ac48e2e4a92f0
MD5 4681d0805faa5b4d747c4e9a60c756b0
BLAKE2b-256 859f4c83b13b03e8d4d4d382a59e76ac8341f05e33754060cae63d774785b01d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page