Added trash script

This commit is contained in:
Nathaniel Landau
2015-06-21 08:51:20 -04:00
parent cd39c3a0fa
commit a4f4b8d9b2

495
bin/trash Executable file
View File

@@ -0,0 +1,495 @@
#!/usr/bin/env bash
# ##################################################
#
# Taken from: https://github.com/morgant/tools-osx
#
# Trash allows trashing of files instead of tempting fate with rm. Correctly handles
# trashing files on other volumes, uses the same filename renaming scheme as Finder
# for duplicate file names, can list trash contents w/disk usage summary, and empty
# trash (including securely) w/confirmation. Does not require Finder to be running.
#
version="1.0.0" # Sets version variable
#
scriptTemplateVersion="1.4.1" # Version of scriptTemplate.sh that this script is based on
#
# HISTORY:
#
# * 2015-06-20 - v1.0.0 - First Creation
#
# ##################################################
# Provide a variable with the location of this script.
scriptPath="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# Source Scripting Utilities
# -----------------------------------
# These shared utilities provide many functions which are needed to provide
# the functionality in this boilerplate. This script will fail if they can
# not be found.
# -----------------------------------
utilsLocation="${scriptPath}/../lib/utils.sh" # Update this path to find the utilities.
if [ -f "${utilsLocation}" ]; then
source "${utilsLocation}"
else
echo "Please find the file util.sh and add a reference to it in this script. Exiting."
exit 1
fi
# trapCleanup Function
# -----------------------------------
# Any actions that should be taken if the script is prematurely
# exited. Always call this function at the top of your script.
# -----------------------------------
function trapCleanup() {
echo ""
if is_dir "${tmpDir}"; then
rm -r "${tmpDir}"
fi
die "Exit trapped." # Edit this if you like.
}
# Set Flags
# -----------------------------------
# Flags which can be overridden by user input.
# Default values are below
# -----------------------------------
quiet=0
printLog=0
verbose=true
force=0
strict=0
debug=0
list=false
emptyTrash=false
secureEmpty=false
args=()
# Set Temp Directory
# -----------------------------------
# Create temp directory with three random numbers and the process ID
# in the name. This directory is removed automatically at exit.
# -----------------------------------
tmpDir="/tmp/${scriptName}.$RANDOM.$RANDOM.$RANDOM.$$"
(umask 077 && mkdir "${tmpDir}") || {
die "Could not create temporary directory! Exiting."
}
# Check for Dependencies
# -----------------------------------
# Arrays containing package dependencies needed to execute this script.
# The script will fail if dependencies are not installed. For Mac users,
# most dependencies can be installed automatically using the package
# manager 'Homebrew'. Mac applications will be installed using
# Homebrew Casks. Ruby and gems via RVM.
# -----------------------------------
homebrewDependencies=()
caskDependencies=()
gemDependencies=()
function mainScript() {
############## Begin Script Here ###################
####################################################
# global variables
user=$(whoami)
uid=$(id -u "$user")
finder_pid=$(ps -u "$user" | grep /System/Library/CoreServices/Finder.app | grep -v grep | awk '{print $1}')
v=''
# 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
}
function listTrash() {
num_volumes=0
total_blocks=0
verbose "listing trash contents"
# 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
info "$size across $num_volumes volume(s)."
safeExit
}
function emptyTheTrash() {
# determine if we can tell Finder to empty trash via AppleScript
if have_scriptable_finder; then
notice "Telling Finder to empty trash... "
if /usr/bin/osascript -e "tell application \"Finder\" to empty trash" ; then
success "Trash has been emptied."
safeExit
else
die "Unable to empty trash."
fi
# if Finder isn't scriptable, we'll manually empty the trash ourselves
else
# confirm that the user wants to empty the trash
seek_confirmation "Are you sure you want to empty the trash (this cannot be undone)?"
if is_confirmed; then
notice "Emptying trash..."
# delete the contents of user's .Trash folder
if [[ $verbose == "1" ]]; then v="-v"; fi
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
success "Trash has been emptied."
fi
safeExit
fi
}
function secureEmptyTheTrash() {
# determine if we can tell Finder to securely empty trash via AppleScript
if have_scriptable_finder; then
notice "Telling Finder to securely empty trash... "
if /usr/bin/osascript -e "tell application \"Finder\" to empty trash with security" ; then
success "Trash has been securely emptied."
safeExit
else
die "Could not empty trash."
fi
# if Finder isn't scriptable, we'll manually empty the trash ourselves
else
if [[ $verbose == "1" ]]; then v="-v"; fi
# confirm that the user wants to securely empty the trash
seek_confirmation "Are you sure you want to securely empty the trash (this REALLY cannot be undone)?"
if is_confirmed; then
# 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
success "Trash has been securely emptied."
fi
safeExit
fi
}
function trashAFile() {
if [[ $verbose == "1" ]]; then v="-v"; fi
# Iterate over all files passed by user
for userFile in "${args[@]}"; do
if [ ! -e "${userFile}" ]; then
warning "$userFile: No such file or directory."
continue
fi
# 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 [ "${userFile:0:1}" = "/" ]; then
file="$userFile"
else
# expand relative to absolute path
verbose "Determining absolute path for '$userFile'... "
file="$(realpath "$userFile")"
if [ $? -ne 0 ]; then
warning "Could not determine absolute path for '$userFile'!"
fi
fi
verbose "Telling Finder to trash '$file'..."
if /usr/bin/osascript -e "tell application \"Finder\" to delete POSIX file \"$file\"" &>/dev/null; then
success "'$userFile' moved to trash"
else
warning "'$userFile' not moved to trash"
safeExit
fi
# Finder isn't available for this user, so don't rely on it (we'll do all the dirty work ourselves)
else
trash="/Users/${user}/.Trash/"
# create the trash folder if necessary
if [ ! -d "$trash" ]; then
mkdir $v "$trash"
fi
# move the file to the trash
if [ ! -e "${trash}${userFile}" ]; then
mv $v "$userFile" "$trash"
else
# determine if the filename has an extension
ext=false
case "$userFile" in
*.*) ext=true ;;
esac
# keep incrementing a number to append to the filename to mimic Finder
i=1
if $ext; then
new="${trash}${userFile%%.*} ${i}.${userFile##*.}"
else
new="${trash}$userFile $i"
fi
while [ -e "$new" ]; do
((i=$i + 1))
if $ext; then
new="${trash}${userFile%%.*} ${i}.${userFile##*.}"
else
new="${trash}${userFile} $i"
fi
done
#move the file to the trash with the new name
mv $v "${userFile}" "$new"
fi
fi
done
}
# run functions
if $list; then listTrash; fi
if $emptyTrash; then emptyTheTrash; fi
if $secureEmpty; then secureEmptyTheTrash; fi
trashAFile
####################################################
############### End Script Here ####################
}
############## Begin Options and Usage ###################
# Print usage
usage() {
echo -n "${scriptName} [OPTION]... [FILE]...
${bold}Trash${reset} allows trashing of files instead of tempting fate with ${bold}rm${reset}. Correctly handles
trashing files on other volumes, uses the same filename renaming scheme as Finder
for duplicate file names, can list trash contents w/disk usage summary, and empty
trash (including securely) w/confirmation. Does not require Finder to be running.
${bold}Options:${reset}
-l , --list list trash contents
-e, --empty empty trash contents
-s, --secure secure empty trash contents
--force Skip all user interaction. Implied 'Yes' to all actions.
-q, --quiet Quiet (no output)
-v, --verbose Output more information. (Items echoed to 'verbose')
-d, --debug Runs script in BASH debug mode (set -x)
-h, --help Display this help and exit
--version Output version information and exit
"
}
# Iterate over options breaking -ab into -a -b when needed and --foo=bar into
# --foo bar
optstring=h
unset options
while (($#)); do
case $1 in
# If option is of type -ab
-[!-]?*)
# Loop over each character starting with the second
for ((i=1; i < ${#1}; i++)); do
c=${1:i:1}
# Add current char to options
options+=("-$c")
# If option takes a required argument, and it's not the last char make
# the rest of the string its argument
if [[ $optstring = *"$c:"* && ${1:i+1} ]]; then
options+=("${1:i+1}")
break
fi
done
;;
# If option is of type --foo=bar
--?*=*) options+=("${1%%=*}" "${1#*=}") ;;
# add --endopts for --
--) options+=(--endopts) ;;
# Otherwise, nothing special
*) options+=("$1") ;;
esac
shift
done
set -- "${options[@]}"
unset options
# Print help if no arguments were passed.
# Uncomment to force arguments when invoking the script
[[ $# -eq 0 ]] && set -- "--help"
# Read the options and set stuff
while [[ $1 = -?* ]]; do
case $1 in
-h|--help) usage >&2; safeExit ;;
--version) echo "$(basename $0) ${version}"; safeExit ;;
-l|--list) list=true ;;
-s|--secure) secureEmpty=true ;;
-e|--empty) emptyTrash=true ;;
-v|--verbose) verbose=1 ;;
-q|--quiet) quiet=1 ;;
-d|--debug) debug=1;;
--force) force=1 ;;
--endopts) shift; break ;;
*) die "invalid option: '$1'." ;;
esac
shift
done
# Store the remaining part as arguments.
args+=("$@")
############## End Options and Usage ###################
# ############# ############# #############
# ## TIME TO RUN THE SCRIPT ##
# ## ##
# ## You shouldn't need to edit anything ##
# ## beneath this line ##
# ## ##
# ############# ############# #############
# Trap bad exits with your cleanup function
trap trapCleanup EXIT INT TERM
# Set IFS to preferred implementation
IFS=$'\n\t'
# Exit on error. Append '||true' when you run the script if you expect an error.
set -o errexit
# Run in debug mode, if set
if [ "${debug}" == "1" ]; then
set -x
fi
# Exit on empty variable
if [ "${strict}" == "1" ]; then
set -o nounset
fi
# Bash will remember & return the highest exitcode in a chain of pipes.
# This way you can catch the error in case mysqldump fails in `mysqldump |gzip`, for example.
set -o pipefail
# Invoke the checkDependenices function to test for Bash packages
# checkDependencies
# Run your script
mainScript
safeExit # Exit cleanly