Safe-rm: Remap `rm` on Macs to `mv _ Trash` so LLMs don't delete data

aayushg1 pts0 comments

macOS safe rm wrapper that moves user files to Trash while preserving common rm semantics · GitHub

/" data-turbo-transient="true" />

Skip to content

-->

Search Gists

Search Gists

Sign in

Sign up

You signed in with another tab or window. Reload to refresh your session.<br>You signed out in another tab or window. Reload to refresh your session.<br>You switched accounts on another tab or window. Reload to refresh your session.

Dismiss alert

{{ message }}

Instantly share code, notes, and snippets.

Divide-By-0/safe-rm-to-trash.sh

Created<br>August 6, 2026 23:44

Show Gist options

Download ZIP

Star

(0)

You must be signed in to star a gist

Fork

(0)

You must be signed in to fork a gist

Embed

Select an option

Embed<br>Embed this gist in your website.

Share<br>Copy sharable link for this gist.

Clone via HTTPS<br>Clone using the web URL.

No results found

Learn more about clone URLs

Clone this repository at &lt;script src=&quot;https://gist.github.com/Divide-By-0/5e1ae59ccf66129dd9ec5ef239f87d65.js&quot;&gt;&lt;/script&gt;

" readonly="readonly" data-autoselect="true" data-target="primer-text-field.inputElement " aria-describedby="validation-e868d8f5-515a-4f75-ba8b-43c619aad877" class="form-control FormControl-monospace FormControl-input FormControl-small rounded-left-0 rounded-right-0 border-right-0" type="text" name="gist-share-url-sized-down" />

Save Divide-By-0/5e1ae59ccf66129dd9ec5ef239f87d65 to your computer and use it in GitHub Desktop.

Embed

Select an option

Embed<br>Embed this gist in your website.

Share<br>Copy sharable link for this gist.

Clone via HTTPS<br>Clone using the web URL.

No results found

Learn more about clone URLs

Clone this repository at &lt;script src=&quot;https://gist.github.com/Divide-By-0/5e1ae59ccf66129dd9ec5ef239f87d65.js&quot;&gt;&lt;/script&gt;

" readonly="readonly" data-autoselect="true" data-target="primer-text-field.inputElement " aria-describedby="validation-9cc1a991-355b-45f7-b7cd-c67de10b3ecf" class="form-control FormControl-monospace FormControl-input FormControl-small rounded-left-0 rounded-right-0 border-right-0" type="text" name="gist-share-url-original" />

Save Divide-By-0/5e1ae59ccf66129dd9ec5ef239f87d65 to your computer and use it in GitHub Desktop.

Download ZIP

macOS safe rm wrapper that moves user files to Trash while preserving common rm semantics

Raw

safe-rm-to-trash.sh

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.<br>Learn more about bidirectional Unicode characters

Show hidden characters

#!/bin/bash

# Move files to macOS Trash (safe rm wrapper).

# Goal: behave like POSIX rm for every case a script or build system can observe

# (exit codes, -f semantics, refusing to remove a directory without -r), while

# routing real user data to ~/.Trash instead of destroying it.

# NOTE: bash 3.2 compatible on purpose (/bin/bash on macOS is 3.2) — no mapfile,

# no ${var,,}, no `set -u` (3.2 errors on "${arr[@]}" when the array is empty).

status=0

force=false

recursive=false

verbose=false

interactive=false

dir_ok=false

end_of_flags=false

flags=()

paths=()

for arg in "$@"; do

if [ "$end_of_flags" = false ] && [ "$arg" = "--" ]; then

# REASON: POSIX `--` ends option parsing. Without this, `rm -rf -- -weirdname`

# treated the filename as a flag and silently never deleted it.

end_of_flags=true

continue

fi

if [ "$end_of_flags" = false ] && [[ "$arg" == -?* ]]; then

[[ "$arg" == *f* ]] && force=true

[[ "$arg" == *r* || "$arg" == *R* ]] && recursive=true

[[ "$arg" == *v* ]] && verbose=true

[[ "$arg" == *i* ]] && interactive=true

[[ "$arg" == *d* ]] && dir_ok=true

flags+=("$arg")

continue

fi

paths+=("$arg")

done

if [ ${#paths[@]} -eq 0 ]; then

# REASON: `rm -f` with no operands must exit 0 (POSIX). Build scripts do

# `rm -f $(FILES)` where FILES can expand to nothing.

[ "$force" = true ] && exit 0

echo "usage: rm [-f | -i] [-dIPRrvW] file ..." >&2

exit 1

fi

# Find the mount point containing $1 by walking up until the device number changes.

# REASON: parsing `df` output breaks on volume names with spaces (e.g. /Volumes/Power Protect).

mount_point_of() {

local p="$1" dev parent

dev="$(stat -f %d -- "$p" 2>/dev/null)" || return 1

while [ "$p" != "/" ]; do

parent="$(dirname -- "$p")"

[ "$(stat -f %d -- "$parent" 2>/dev/null)" != "$dev" ] && break

p="$parent"

done

printf '%s' "$p"

# Move $1 to the Trash using the same API Finder uses (NSFileManager -trashItemAtURL:).

# REASON: a raw rename(2) into ~/.Trash fails with EDEADLK ("Resource deadlock avoided")

# for items under a FileProvider/iCloud domain such as ~/Documents (and its .nosync

# subtrees), and also under concurrent modification of the same tree. trashItemAtURL is

# domain-aware and routes to the correct trash instead of doing a cross-domain rename.

# REASON: osascript is wrapped in `timeout 60 taskpolicy...

gist trash true clone false data

Related Articles