Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

executable file 335 lines (315 sloc) 11.266 kb
#!/bin/bash
#
# trash - Move files to the appropriate .Trash file on Mac OS X. (Intended
# as an alternative to 'rm' which immediately deletes the file.)
#
# v0.1 2007-05-21 - Morgan Aldridge <morgant@makkintosshu.com>
# Initial version.
# v0.2 2010-10-26 - Morgan Aldridge
# Use appropriate .Trashes folder when trashing files
# on other volumes. Create trash folder(s) if necessary.
# v0.2.1 2010-10-26 - Morgan Aldridge
# No longer using bash built-in regexp support in hopes
# of support Mac OS X 10.4 and earlier.
# v0.3 2010-12-07 - Morgan Aldridge
# Correctly handle full volume path which is root volume.
# Now increments filename if filename already exists in
# trash folder (à la Finder).
# v0.4 2011-06-02 - Morgan Aldridge
# Option to list trash contents w/disk usage total. Allows
# emptying of trash w/confirmation, incl. secure empty.
# v0.5 2011-07-04 - Morgan Aldridge
# Support for trashing/emptying using Finder via AppleScript,
# when available.
# v0.5.1 2012-03-29 - Matt Brictson & Morgan Aldridge
# Fixed bug where cwd would get trashed instead of specified
# file/path if it contained a relative path when trashing
# using Finder via AppleScript.
# v0.5.2 2012-11-30 - Morgan Aldridge
# Merged in fix for realpath() implementation to fix
# assumption that relative paths were in root directory.
# Also correctly warns of missing files/directories.
#
# global variables
verbose=false
user=$(whoami)
uid=$(id -u "$user")
finder_pid=$(ps -u "$user" | grep Finder.app | grep -v grep | awk '{print $1}')
v=''
# print usage instructions (help)
function usage() {
printf "Usage: trash [options] file ...\n"
printf " -v verbose output\n"
printf " -h print these usage instructions\n"
printf " -l list trash contents\n"
printf " -e empty trash contents\n"
printf " -s secure empty trash contents\n"
}
# determine whether we can script the Finder or not
function have_scriptable_finder() {
# We must have a valid PID for Finder, plus we cannot be in `screen` (another thing that's broken)
if [[ ($finder_pid -gt 1) && ("$STY" == "") ]]; then
true
else
false
fi
}
##
## Convert a relative path to an absolute path.
##
## From http://github.com/morgant/realpath
##
## @param string the string to converted from a relative path to an absolute path
## @returns Outputs the absolute path to STDOUT, returns 0 if successful or 1 if an error (esp. path not found).
##
function realpath()
{
local success=true
local path="$1"
# make sure the string isn't empty as that implies something in further logic
if [ -z "$path" ]; then
success=false
else
# start with the file name (sans the trailing slash)
path="${path%/}"
# if we stripped off the trailing slash and were left with nothing, that means we're in the root directory
if [ -z "$path" ]; then
path="/"
fi
# get the basename of the file (ignoring '.' & '..', because they're really part of the path)
local file_basename="${path##*/}"
if [[ ( "$file_basename" = "." ) || ( "$file_basename" = ".." ) ]]; then
file_basename=""
fi
# extracts the directory component of the full path, if it's empty then assume '.' (the current working directory)
local directory="${path%$file_basename}"
if [ -z "$directory" ]; then
directory='.'
fi
# attempt to change to the directory
if ! cd "$directory" &>/dev/null ; then
success=false
fi
if $success; then
# does the filename exist?
if [[ ( -n "$file_basename" ) && ( ! -e "$file_basename" ) ]]; then
success=false
fi
# get the absolute path of the current directory & change back to previous directory
local abs_path="$(pwd -P)"
cd "-" &>/dev/null
# Append base filename to absolute path
if [ "${abs_path}" = "/" ]; then
abs_path="${abs_path}${file_basename}"
else
abs_path="${abs_path}/${file_basename}"
fi
# output the absolute path
echo "$abs_path"
fi
fi
$success
}
# see if any arguments were passed in
if [ $# -gt 0 ]; then
# if so, step through them all and process them
while [ $# -gt 0 ]; do
# see if the user intended us to run in verbose mode
if [ "$1" = "-v" ]; then
shift
verbose=true
# see if the user requested help
elif [ "$1" = "-h" ]; then
shift
usage
exit
# see if the user requested a list of trash contents
elif [ "$1" = "-l" ]; then
shift
num_volumes=0
total_blocks=0
# list file contents & calculate size for user's .Trash folder
if find "/Users/${user}/.Trash" -depth 1 ! -depth 0; then
num_volumes=$(( $num_volumes + 1 ))
blocks=$(du -cs "/Users/${user}/.Trash" | tail -n 1 | cut -f 1)
total_blocks=$(( $total_blocks + $blocks ))
fi
# list file contents & calculate size for volume-specific .Trashes folders
for file in /Volumes/*; do
if [ -d "$file" ]; then
folder="${file}/.Trashes/${uid}"
if [ -d "${folder}" ]; then
if find "$folder" -depth 1 ! -depth 0; then
num_volumes=$(( $num_volumes + 1 ))
blocks=$(du -cs "$folder" | tail -n 1 | cut -f 1)
total_blocks=$(( $total_blocks + $blocks ))
fi
fi
fi
done
# convert blocks to human readable size
size=0
if (( $total_blocks >= 2097152 )); then
size=$(bc <<< "scale=2; $total_blocks / 2097152")
size="${size}GB"
elif (( $total_blocks >= 2048 )); then
size=$(bc <<< "scale=2; $total_blocks / 2048")
size="${size}MB"
else
size=$(bc <<< "scale=2; $total_blocks / 2")
size="${size}K"
fi
printf "%s across %s volume(s).\n" "$size" $num_volumes
exit
# see if the user requested to empty the trash contents
elif [ "$1" = "-e" ]; then
shift
# determine if we can tell Finder to empty trash via AppleScript
if have_scriptable_finder; then
if $verbose; then printf "Telling Finder to empty trash... "; fi
if /usr/bin/osascript -e "tell application \"Finder\" to empty trash" ; then
if $verbose; then printf "Done.\n"; fi
exit
else
if $verbose; then printf "ERROR!\n"; fi
exit 1
fi
# if Finder isn't scriptable, we'll manually empty the trash ourselves
else
if $verbose; then v="-v"; fi
# confirm that the user wants to empty the trash
printf "Are you sure you want to empty the trash (this cannot be undone)? "
read confirm
if [ "$confirm" = "y" ]; then
printf "Emptying trash...\n"
# delete the contents of user's .Trash folder
find "/Users/${user}/.Trash" -depth 1 ! -depth 0 -print0 | xargs -0 rm $v -r
# delete the contents of the volume-specific .Trashes folders
for file in /Volumes/*; do
if [ -d "$file" ]; then
folder="${file}/.Trashes/${uid}"
if [ -d "$folder" ]; then
find "$folder" -depth 1 ! -depth 0 -print0 | xargs -0 rm $v -r
fi
fi
done
printf "Done.\n"
fi
exit
fi
# see if the user requested to securely empty the trash contents
elif [ "$1" = "-s" ]; then
shift
# determine if we can tell Finder to securely empty trash via AppleScript
if have_scriptable_finder; then
if $verbose; then printf "Telling Finder to securely empty trash... "; fi
if /usr/bin/osascript -e "tell application \"Finder\" to empty trash with security" ; then
if $verbose; then printf "Done.\n"; fi
exit
else
if $verbose; then printf "ERROR!\n"; fi
exit 1
fi
# if Finder isn't scriptable, we'll manually empty the trash ourselves
else
if $verbose; then v="-v"; fi
# confirm that the user wants to securely empty the trash
printf "Are you sure you want to securely empty the trash (this REALLY cannot be undone)? "
read confirm
if [ "$confirm" = "y" ]; then
printf "Securely emptying trash...\n"
# securely delete the contents of user's .Trash folder
find "/Users/${user}/.Trash" -depth 1 ! -depth 0 -print0 | xargs -0 srm $v -r
# securely delete the contents of the volume-specific .Trashes folders
for file in /Volumes/*; do
if [ -d "$file" ]; then
folder="${file}/.Trashes/${uid}"
if [ -d "$folder" ]; then
find "$folder" -depth 1 ! -depth 0 -print0 | xargs -0 srm $v -r
fi
fi
done
printf "Done.\n"
fi
exit
fi
# handle remaining arguments as if they were files
else
#printf "argument: '%s'\n" $1
#printf "destination: '%s'\n" $TRASH
if $verbose; then v="-v"; fi
# does the file we're trashing exist?
if [ ! -e "$1" ]; then
printf "trash: '%s': No such file or directory\n" "$1"
else
# determine if we'll tell Finder to trash the file via AppleScript (very easy, plus free undo
# support, but Finder must be running for the user and is DOES NOT work from within `screen`)
if have_scriptable_finder; then
# determine whether we have an absolute path name to the file or not
if [ "${1:0:1}" = "/" ]; then
file="$1"
else
# expand relative to absolute path
if $verbose; then printf "Determining absolute path for '%s'... " "$1"; fi
file="$(realpath "$1")"
if [ $? -eq 0 ]; then
if $verbose; then printf "Done.\n"; fi
else
if $verbose; then printf "ERROR!\n"; fi
fi
fi
if $verbose; then printf "Telling Finder to trash '%s'... " "$file"; fi
if /usr/bin/osascript -e "tell application \"Finder\" to delete POSIX file \"$file\"" ; then
if $verbose; then printf "Done.\n"; fi
else
if $verbose; then printf "ERROR!\n"; fi
fi
# Finder isn't available for this user, so don't rely on it (we'll do all the dirty work ourselves)
else
# determine whether we should be putting this in a volume-specific .Trashes or user's .Trash
IFS=/ read -r -d '' _ _ vol _ <<< "$1"
if [[ ("${1:0:9}" == "/Volumes/") && (-n "$vol") && ($(readlink "/Volumes/$vol") != "/") ]]; then
trash="/Volumes/${vol}/.Trashes/${uid}/"
else
trash="/Users/${user}/.Trash/"
fi
# create the trash folder if necessary
if [ ! -d "$trash" ]; then
mkdir $v "$trash"
fi
# move the file to the trash
if [ ! -e "${trash}$1" ]; then
mv $v "$1" "$trash"
else
# determine if the filename has an extension
ext=false
case "$1" in
*.*) ext=true ;;
esac
# keep incrementing a number to append to the filename to mimic Finder
i=1
if $ext; then
new="${trash}${1%%.*} ${i}.${1##*.}"
else
new="${trash}$1 $i"
fi
while [ -e "$new" ]; do
((i=$i + 1))
if $ext; then
new="${trash}${1%%.*} ${i}.${1##*.}"
else
new="${trash}$1 $i"
fi
done
#move the file to the trash with the new name
mv $v "$1" "$new"
fi
fi
fi
shift
fi
done
else
printf "No files were specified to be moved to the trash.\n\n"
usage
fi
Jump to Line
Something went wrong with that request. Please try again.