Shell 实现终端 rm 回收站效果
本次实现的 rm 脚本的功能有:
- 回收站收容删除的文件
- 脚本记录删除的文件绝对路径和时间戳
- 可根据脚本恢复删除的文件,并且可以只根据文件名匹配回收站所有的文件进行选择
- 可直接删除文件
- 可同时接收多个参数
- 清空回收站
后续会更新定时清空回收站的功能。
TRASH_DIR="$HOME/.trash"
LOG_FILE="${TRASH_DIR}/.log"
VERSION="1.0"
NAME="rm"
# trash dir path: $TRASH_DIR
# create dir if not exists
if [ ! -d "$TRASH_DIR" ]
then mkdir -v "$TRASH_DIR"
fi
if [ ! -f "$LOG_FILE" ]
then touch "$LOG_FILE"
fi
error() {
#echo -e "\e[1;31m[ERROR] ${1}\e[0m"
:
}
debug() {
#echo -e "\e[1;32m[DEBUG] ${1}\e[0m"
:
}
usage()
{
echo -e "Usage: delete [options] [file1 file2 file3....]\n"
echo "delete is a simple command line interface for deleting file to custom trash."
echo "Options:"
echo " -c | --clear Empty the trash"
echo " -f | --force Force delete file instead of moving into trash"
echo " -r | --restore Restore the file in the trash"
echo " -l | --list List the files in the trash"
echo " -p | --print Print log file"
echo " -h | --help Show this help message and exit"
echo " -v | --version Show program's version number and exit"
echo " -t | --trash Set default trash can path, this option has peermanent effect"
}
version() {
echo "${NAME} (Trash Collection) 1.0"
}
print_log() {
cat "${LOG_FILE}"
}
list() {
ls "${TRASH_DIR}"
}
set_trash() {
if [[ $# -eq 0 ]]; then
echo "${NAME}: '-t' option missing trash can path"
exit 1
fi
if [[ $# -gt 1 ]]; then
echo "Only one trash can path is acceptable"
exit 1
fi
fullpath="$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
if [ -f "${fullpath}" ]; then
echo "${NAME}: cannot set directory '$1' as trash can: File exists"
exit 1
fi
if [ ! -d "${fullpath}" ]; then
echo "Target directory doesn't exist"
echo -ne "Do you want to create this directory? [y/n]: \a"
read reply
if [ ! $reply = "y" -o ! $reply = "Y" ]; then
exit 1
fi
mkdir -p "${fullpath}"
if [ ! $? -eq 0 ]; then
echo "mkdir ${fullpath} failed"
exit 1
fi
fi
TRASH_DIR="${fullpath}"
LOG_FILE="${fullpath}/.log"
touch "${LOG_FILE}"
fullpath=${fullpath//\//\\\/} #replace all / with \/ in fullpath
sed -i "0,/TRASH_DIR/{s/TRASH_DIR=.*/TRASH_DIR=\"${fullpath}\"/}" "$0"
}
clear_trash() {
if [ $(uname -s) = "Darwin" ]; then
echo "Clear trash is not permitted in Mac. Trash can is regularly cleared."
exit 0
fi
echo -ne "Are you sure you want to clear trash? [y/n]:\a"
read reply
case $reply in
'y'|'Y')
for file in $(ls -A "${TRASH_DIR}"); do
echo "Removing ${TRASH_DIR}/${file} foreverly."
rm -rf "$TRASH_DIR/$file"
done
echo -ne "Do you want to clear all logs? [y/n]:\a"
read reply
if [ $reply = "y" -o $reply = "y" ]; then
echo "clearing all logs"
cat /dev/null > "${LOG_FILE}"
fi
echo "Done.";;
'n'|'N') echo "Cancelling deleteing.";;
*) echo "Unrecognized character.";;
esac
}
force_delete() {
if [ $# -eq 0 ]; then
echo "${NAME}: missing operand"
exit 0
fi
echo -ne "Are you sure you want to foreverly delete file[s]? [y/n]:\a"
read reply
case "$reply" in
'y'|'Y')
for file in $@; do
rm -rf "${file}"
done
echo "Done.";;
'n'|'N') echo "Cancelling deleteing.";;
*) echo "Unrecognized character.";;
esac
}
delete() {
for file in $@; do
debug "deleting ${file}"
if [ ! -f "$file" -a ! -d "$file" ]
then
echo "${NAME}: cannot remove '$file': No such file or directory"
continue
fi
# check if user want to move file larger than 2GB to trash
if [ -f "$file" -a $(du -b "$file" | cut -f1) -gt 2147483648 ]; then
echo "File '$file' size is larger than 2GB."
echo -ne "Do you still want to move it to trash? [y/n]: \a"
read reply
if [ ! $reply = "y" -a ! $reply = "Y" ]; then
echo "moving aborted"
exit 0
fi
fi
filename="$(basename "$file")"
fullpath="$(cd "$(dirname "$file")"; pwd)/${filename}"
now="$(date +%a%b%d%H%M%S%Y)"
if [ -f "$TRASH_DIR/${filename}_${now}" -o -d "$TRASH_DIR/${filename}_${now}" ]; then
sleep 1
now="$(date +%a%b%d%H%M%S%Y)"
fi
mv $file "$TRASH_DIR/${filename}_${now}"
debug "new filename ${filename}_${now}"
if [ $? -eq 0 ]; then
echo "${fullpath}_${now}" >> ${LOG_FILE}
else echo -ne "mv $file to $TRASH_DIR/${filename} failed\a"
fi
done
}
restore() {
if [ $# -eq 0 ]; then
echo "${NAME}: restore missing operand"
exit 1
fi
for file in $@; do
filename="$(basename "$file")"
fullpath="$(cd "$(dirname "$file")"; pwd)/${filename}"
if [ $filename = $file ]; then #if only the basename is provided
debug "match base name $filename"
pattern="s/\(.*\)\/${filename}_\([a-zA-Z]\{3\}\)\([a-zA-Z]\{3\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)\([0-9]\{4\}\)/\1 \2 \3 \4 \5 \6 \7 \8/"
logs=$(cat "${LOG_FILE}")
candidates=()
number=0
for line in $logs; do
res=( $(echo $line | sed "$pattern") )
debug "match result: [${#res[@]}] ${res[@]}"
if [ ${#res[@]} -gt 1 ]; then
echo -e "\e[0;33m[${number}]\e[0m ${res[0]}/${filename} \e[0;36m[${res[1]} ${res[2]} ${res[3]} ${res[4]}:${res[5]}:${res[6]} ${res[7]}]\e[0m"
candidates+=${res}
let number++
fi
done
echo -n "enter the number of your target: "
read reply
if [ $reply -ge 0 -a $reply -lt $number ]; then
let index=reply\*8
fullpath="${candidates[$index]}/${filename}"
if [ -f "${fullpath}" -o -d "${fullpath}" ]; then
echo "${NAME}: Restore target exists."
echo -ne "Do you want to override this file? [y/n]: \a"
read reply
if [ ! $reply = "y" -a ! $reply = "Y"]; then
exit 0
fi
fi
trash_file="${filename}_"
for i in $(seq 1 7); do
trash_file+="${res[`expr index+${i}`]}"
done
if [ ! -f "${TRASH_DIR}/${trash_file}" -a ! -d "${TRASH_DIR}/${trash_file}" ]; then
error "Trash file ${trash_file} does not exist"
exit 1
fi
mv "${TRASH_DIR}/${trash_file}" "${fullpath}"
sed -i "/${trash_file}/d" "${LOG_FILE}"
else
echo -e "\e[0;31mrestore: reply '$reply' is out of bounds\e[0m"
exit 1
fi
fi
done
}
option="d"
args=()
declare -A -r funcs=(
["d"]="delete"
["h"]="usage"
["v"]="version"
["p"]="print_log"
["l"]="list"
["c"]="clear_trash"
["r"]="restore"
["t"]="set_trash"
["f"]="force_delete"
)
if [[ $# -eq 0 ]]; then
echo "${NAME}: missing operand"
echo "Try '${NAME} -h' for more information"
exit 0
fi
# process arguments
while [[ $# -gt 0 ]]; do
debug "arg $1"
debug "args: ${args[@]}"
case $1 in
-h | -c | -f | -r | -v | -l | -p | -t)
debug "calling ${funcs[$option]} ${args[@]}"
if [ -z ${funcs[$option]} ]; then
error "invalid option $option"
exit 1
fi
${funcs[$option]} ${args[@]}
option=${1:1:1}
args=()
shift;;
--help | --clear | --force | --restore | --version | --list | --print | --trash)
debug "calling ${funcs[$option]} ${args[@]}"
if [ -z ${funcs[$option]} ]; then
error "invalid option $option"
exit 1
fi
${funcs[$option]} ${args[@]}
option=${1:2:1}
args=()
shift;;
-* | --*)
echo "${NAME}: invalid option '$1'"
echo "Try '${NAME} -h' for more informtion"
exit 1;;
*) args+=("$1"); shift;;
esac
done
debug "calling ${funcs[$option]} ${args[@]}"
if [ -z ${funcs[$option]} ]; then
error "invalid option $option"
exit 1
fi
${funcs[$option]} ${args[@]}
WELCOME TO THE MACHINE