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

Platform: Linux FreeDesktop trash only (home trash + per-mount trash dirs). Not a Windows/macOS system-trash wrapper, and not a colored 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.

macOS and Windows have no prebuilt FreeDesktop assets (and Windows system trash is out of scope for this tool's niche). Use from-source only where the Linux FreeDesktop code path applies.

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

Restores the item trashed from PATH (or picks among items trashed from under the current directory when PATH is omitted). A single match restores directly; multiple matches list with indices for interactive selection (requires a TTY on stdin). 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).

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 and non-goals

  • No pretty TUI: no colored tables or fuzzy restore (see trashy/gtrash).
  • Linux FreeDesktop only: no Windows Recycle Bin / macOS Trash backends.
  • 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 Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

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

rtrash-0.1.1-cp314-cp314-manylinux_2_34_x86_64.whl (317.5 kB view details)

Uploaded CPython 3.14manylinux: glibc 2.34+ x86-64

rtrash-0.1.1-cp313-cp313-manylinux_2_34_x86_64.whl (317.5 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.34+ x86-64

rtrash-0.1.1-cp312-cp312-manylinux_2_34_x86_64.whl (317.9 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.34+ x86-64

rtrash-0.1.1-cp311-cp311-manylinux_2_34_x86_64.whl (318.6 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.34+ x86-64

rtrash-0.1.1-cp310-cp310-manylinux_2_34_x86_64.whl (318.9 kB view details)

Uploaded CPython 3.10manylinux: glibc 2.34+ x86-64

File details

Details for the file rtrash-0.1.1-cp314-cp314-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for rtrash-0.1.1-cp314-cp314-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 782c044042c28fb68785460fe0a4b4e513ae2bf7a0db8d694827cc600dce3fe2
MD5 5391b01f4a67e10fe1ebe49a6deaa65f
BLAKE2b-256 00b33fc78c42d1168f88cd63b7c35544bb3285036370d2d51ae5fd53f86aaa6d

See more details on using hashes here.

File details

Details for the file rtrash-0.1.1-cp313-cp313-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for rtrash-0.1.1-cp313-cp313-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 8562eed3b9868cf39483f9d15aabe816277dc7c4d7de992eafde21667e3fc7e6
MD5 ed23493bfad2c762d3655441db13f43d
BLAKE2b-256 3abd0eff6c32fccb88692b36bfb45c1b9a6e1c13966f5830a1200ae235d0330e

See more details on using hashes here.

File details

Details for the file rtrash-0.1.1-cp312-cp312-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for rtrash-0.1.1-cp312-cp312-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 80a6f3ffdcfa9bffce1e2d7e16ee7c5ec90d0f365b0f20f007e20320dcf3a81a
MD5 1af26bd9297f73ffd734090866225aef
BLAKE2b-256 bc10d62fc9adfb13430be7cafecb949fcc7e62f182b443c5e024c0e3273410dd

See more details on using hashes here.

File details

Details for the file rtrash-0.1.1-cp311-cp311-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for rtrash-0.1.1-cp311-cp311-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 8460c95ef7110d4a9b968787c18153be84f5327afc7bffd710ccbf44b5949b2c
MD5 5176ff20283d72bb8f7d9274a8592268
BLAKE2b-256 fb8e08ea063be3ccd952cd111832295b35a3590353bb96b0c43a1b5f85361345

See more details on using hashes here.

File details

Details for the file rtrash-0.1.1-cp310-cp310-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for rtrash-0.1.1-cp310-cp310-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 a2865e7f0e8cded88ea4b8409c3745bdf613f818dc78efd5d41cc91b383f8cdf
MD5 e21ecf6accdf804d34bdf6de18fe0f62
BLAKE2b-256 be5350ad04df8eed40ac125ca890757a1bbf7881f8f16857fd999d136267510f

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