mirror of
https://github.com/natelandau/shell-scripting-templates.git
synced 2025-11-10 14:13:45 -05:00
Added trash script
This commit is contained in:
495
bin/trash
Executable file
495
bin/trash
Executable 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
|
||||||
Reference in New Issue
Block a user