#!/bin/bash # # trash — move files to the Trash following the FreeDesktop.org Trash Spec 1.0. # # Chooses the correct trash directory regardless of distro / desktop: # * Items on the same filesystem as $HOME go to the "home trash": # ${XDG_DATA_HOME:-$HOME/.local/share}/Trash # * Items on a different filesystem go to a top-dir trash on that mount: # /.Trash/$uid (if it is a valid, sticky, non-symlink dir) # /.Trash-$uid (fallback, always valid to create) # # Also writes the matching .trashinfo metadata so file managers actually # display and can restore the trashed items. trash() { local uid=$UID local home_trash="${XDG_DATA_HOME:-$HOME/.local/share}/Trash" local home_top home_top=$(df --output=target -- "$HOME" 2>/dev/null | tail -n 1) local item abs top trash_dir files_dir info_dir base stem ext candidate n local deletion_date info_path for item in "$@"; do if [ ! -e "$item" ] && [ ! -L "$item" ]; then echo "Error: '$item' does not exist." >&2 continue fi # Resolve to an absolute path for the .trashinfo Path field. abs=$(readlink -f -- "$item") || abs=$item # Decide which trash to use based on the filesystem the item lives on. top=$(df --output=target -- "$item" 2>/dev/null | tail -n 1) if [ -z "$top" ] || [ "$top" = "$home_top" ]; then trash_dir=$home_trash else if [ -d "$top/.Trash" ] && [ ! -L "$top/.Trash" ] && [ "$(( $(stat -c %a -- "$top/.Trash" 2>/dev/null || echo 0) & 3777 ))" -eq 1777 ]; then trash_dir="$top/.Trash/$uid" else trash_dir="$top/.Trash-$uid" fi # If we can't create a top-dir trash on this mount (e.g. read-only / # root or restricted top dir), fall back to the home trash, which is # always writable. This matches what GVFS does. if ! mkdir -p -- "$trash_dir/files" "$trash_dir/info" 2>/dev/null; then trash_dir=$home_trash fi fi files_dir=$trash_dir/files info_dir=$trash_dir/info if ! mkdir -p -- "$files_dir" "$info_dir"; then echo "Error: cannot create trash dir '$trash_dir' for '$item'." >&2 continue fi # Pick a unique name, preserving the extension (and handling none). base=$(basename -- "$item") if [[ $base == *.* ]]; then stem=${base%.*} ext=.${base##*.} else stem=$base ext= fi candidate=$base n=1 while [ -e "$files_dir/$candidate" ] || [ -e "$info_dir/$candidate.trashinfo" ]; do candidate=${stem}_$n$ext n=$((n + 1)) done # Write the .trashinfo first; if mv fails, remove it to stay consistent. deletion_date=$(date +%Y-%m-%dT%H:%M:%S) info_path=$info_dir/$candidate.trashinfo { printf '[Trash Info]\n' printf 'Path=%s\n' "$abs" printf 'DeletionDate=%s\n' "$deletion_date" } > "$info_path" if mv -f -- "$item" "$files_dir/$candidate"; then echo "Moved '$item' to '$files_dir/$candidate'" else echo "Error: failed to move '$item'." >&2 rm -f -- "$info_path" fi done } trash "$@"