目录
[TOC]
新版
不要用我这个"垃圾"脚本了,发现2007年(Tue Jul 24 11:46:14 2007 +0000)就有人做过了,而且还更好!
我在想怎么每个磁盘都做一个垃圾桶目录的时候,就发现了这个现成的仓库。。
这是github地址: https://github.com/andreafrancia/trash-cli
这里是使用说明: linux垃圾箱防止误删
(以下旧版已废弃)
效果展示
一、问题描述
有时候我们会误删一些文件,这时候我们可以使用回收站来恢复误删的文件。
二、偷懒解决方案(一键脚本)
觉得麻烦也可以直接复制如下代码进入终端回车即可,效果和不偷懒方案
一样的
wget -qO remove.sh https://git.dreamdusk.com/Dean/new_rm/raw/branch/main/new_rm.sh && chmod +x remove.sh && ./remove.sh
运行以后输入rm -h
查看帮助
三、不偷的懒解决方案
将本文件复制到任意位置(名字任意,比如是XXX),然后执行 chmod +x XXX
,最后执行一次 bash XXX
即可。(如果自动安装失败的话,也可以手动:1.在你使用的shell的配置文件( ~. bashrc 或 ~.zshrc)中添加一行 alias rm='/usr/local/bin/new_rm'
, 2.将此脚本重命名为 new_rm
,并复制到 /usr/local/bin/
目录下, 3.重新开启一个终端输入rm即可。)
其实只是把安装一键脚本的过程手动化了,,,反正就是运行一次下面的脚本就安装好了,要卸载就rm -uninstall
#!/bin/bash
# 项目: new_rm
# 作者: BrettDean
# 说明: 一个用于替代系统自带的 rm 命令的脚本,能够将删除的文件移动到回收站中,以便于恢复。
# 依赖: crontab
# 安装: 将本文件复制到任意位置(名字任意,比如是XXX),然后执行 `chmod +x XXX`,最后执行一次 `bash XXX` 即可。(如果自动安装失败的话,也可以手动:1.在你使用的shell的配置文件( ~. bashrc 或 ~.zshrc)中添加一行 `alias rm='/usr/local/bin/new_rm'`, 2.将此脚本重命名为 `new_rm`,并复制到 `/usr/local/bin/` 目录下, 3.重新开启一个终端输入rm即可。)
# 卸载: 执行 `rm -uninstall` 即可。
# 更新: 如果有新版的话,直接再运行一次新版的脚本即可(也可按照这个方法恢复旧版,只要你运行的不是`/usr/local/bin/new_rm`,那么就会更新`/usr/local/bin/new_rm`为你当前运行的版本)。
# 日期: 2023-04-26
# 版本: 1.0.9
VERSION=1.0.9
# 帮助文本
usage() {
echo ""
echo "用法: rm [选项] 文件(一个或多个,用空格分隔)"
echo ""
echo "可用选项:"
echo " -config --configeration 显示回收站的配置信息"
echo " -cron --crontab 设置定时清空回收站"
echo " -del --delete 删除垃圾桶中的指定文件"
echo " -e --empty 清空回收站所有文件"
echo " -f -rf --force 真正删除指定的文件或目录"
echo " -h --help 显示此帮助信息并退出"
echo " -ls -l --list 显示回收站中的所有文件"
echo " -ra --restore-all 恢复垃圾桶目录中的所有文件"
echo " -re --restore 尝试恢复指定的文件或目录"
echo " -set --set-path 修改垃圾桶路径"
echo " -uninstall --uninstall 卸载本脚本"
echo " -update --update 更新本脚本"
echo " -v --version 显示版本信息并退出"
echo ""
echo "示例:rm /path/to/file 或者 rm -r /path/to/file 将指定的文件或目录移入回收站"
}
# 初始化运行环境
function init_new_rm() {
# '''
# 该函数主要功能是初始化一个名为 `new_rm` 的别名,将其指向 `/usr/local/bin/new_rm`,以替代系统自带的 `rm` 命令。该脚本能够自动识别用户当前使用的 shell 类型(Bash 或 Zsh),并自动在用户的 rc(runtime configuration)文件中添加或修改相应的别名定义。以下是该脚本的详细解释:
# 1.定义函数 `init_new_rm()`,用于初始化 `new_rm` 别名。
# 2.通过命令 `grep "^$USER:" /etc/passwd | cut -d: -f7` 获取当前用户的默认 shell,赋值给变量 $DEFAULT_SHELL。
# 3.如果用户的默认 shell 是 Bash,则设置变量 `$shell` 为 "bash",并通过命令 `grep -v '.*#.*' ~/.bashrc | grep 'alias' | sed -E 's/^[[:space:]]\{0,\}\balias\b/alias/' | wc -l` 统计当前用户在 Bash 中定义的所有别名数量,赋值给变量 `$alias_number`。如果用户的默认 shell 是 Zsh,则设置变量 `$shell` 为 "zsh",并通过命令 `grep -v '.*#.*' ~/.zshrc | grep 'alias' | sed -E 's/^[[:space:]]\{0,\}\balias\b/alias/' | wc -l` 统计当前用户在 Zsh 中定义的所有rm别名数量,赋值给变量 `$alias_number`。
# 4.如果用户的默认 shell 不是 Bash 或 Zsh,则输出错误信息并退出脚本。
# 5.将变量 `$shell` 与字符串 "rc" 进行拼接,得到用户的 rc 文件路径,赋值给变量 `$rc_file`。
# 6.如果 `/usr/local/bin/new_rm` 不存在,则将当前脚本复制至该路径下。
# 7.如果 `/usr/local/bin/new_rm` 存在,且别名数量为 0(即用户没有定义任何别名),则在用户的 rc 文件中添加一行 `alias rm='/usr/local/bin/new_rm'`,以初始化 `new_rm` 别名。
# 8.如果 `/usr/local/bin/new_rm` 存在,且别名数量为 1,则判断该别名是否正确。如果别名正确,则不需要再次初始化;否则,删除 rc 文件中所有关于 `rm` 的别名定义,并重新添加一行正确的 `alias rm='/usr/local/bin/new_rm'`,最后再次调用 `init_new_rm()` 函数进行初始化。
# 9.如果 `/usr/local/bin/new_rm` 存在,且别名数量大于 1,则删除 rc 文件中所有关于 `rm` 的别名定义,并重新添加一行正确的 `alias rm='/usr/local/bin/new_rm'`,最后再次调用 `init_new_rm()` 函数进行初始化。
# '''
# 判断默认 shell
DEFAULT_SHELL=$(grep "^$USER:" /etc/passwd | cut -d: -f7)
if echo "$DEFAULT_SHELL" | grep -q "bash"; then
# echo "默认 shell 是 Bash"
shell="bash"
# 获取bash中定义的所有别名数量
alias_number=$(grep -v '.*#.*' ~/.bashrc | grep 'alias' | sed -E 's/^[[:space:]]\{0,\}\balias\b/alias/' | wc -l)
elif echo "$DEFAULT_SHELL" | grep -q "zsh"; then
# echo ""
# echo ""
# echo ""
# echo "默认 shell 是 Zsh"
shell="zsh"
# 获取zsh中定义的所有别名数量
alias_number=$(grep -v '.*#.*' ~/.zshrc | grep 'alias' | sed -E 's/^[[:space:]]\{0,\}\balias\b/alias/' | wc -l)
else
# echo "默认 shell 是 $DEFAULT_SHELL"
echo "暂不支持 $DEFAULT_SHELL"
exit 1
fi
declare shell=$shell
# 拼接rc配置文件
rc_file="$HOME/."$shell"rc"
init_trash_path_and_time_interval
# 如果 $TRASH_DIR 不存在的话就创建它
if [ ! -d "$TRASH_DIR" ]; then
mkdir -p "$TRASH_DIR"
fi
# 在上面的代码中,已经获取了 alias_number和shell和rc_file,他们分别是别名数量、shell类型和rc配置文件
# echo 'shell='$shell
# echo 'alias_number='$alias_number
# echo 'rc_file='$rc_file
# 如果/usr/local/bin/new_rm存在
if [ -e /usr/local/bin/new_rm ]; then
# 如果当前运行的脚本不是 /usr/local/bin/new_rm
if [[ "$0" != "/usr/local/bin/new_rm" ]]; then
# 删除/usr/local/bin/new_rm
rm -rf /usr/local/bin/new_rm
fi
fi
# 如果/usr/local/bin/new_rm不存在,则将本文件复制过去
if [ ! -e /usr/local/bin/new_rm ]; then
# echo "/usr/local/bin/new_rm不存在,已将本文件复制过去"
cp -f $0 /usr/local/bin/new_rm
chmod +x /usr/local/bin/new_rm
fi
# 如果/usr/local/bin/new_rm存在,但是别名数量 等于 0,则在 $rc_file 中添加别名
if [ -e /usr/local/bin/new_rm ] && [ $alias_number -eq 0 ]; then
# echo "/usr/local/bin/new_rm存在;rc文件中缺少别名"
# 在 $rc_file 中添加一行 `alias rm='/usr/local/bin/new_rm'`
# echo "向 $rc_file 中添加正确的别名1"
echo "alias rm='/usr/local/bin/new_rm'" >>$rc_file
is_add_alias=1
# fi
# 如果/usr/local/bin/new_rm存在,且别名数量 等于 1,则不需要初始化,直接退出初始化函数
elif [ -e /usr/local/bin/new_rm ] && [ $alias_number -eq 1 ]; then # 虽然经过上面的if,已经知道这里/usr/local/bin/new_rm肯定存在,但是还是写一下
# echo "/usr/local/bin/new_rm存在;关于rm的alias别名总数量 等于 1"
# 如果别名数量为1,且别名正确`alias rm='/usr/local/bin/new_rm'`,则不需要初始化
if [[ "$(grep -v '.*#.*' "$rc_file" | grep 'alias' | sed -E 's/^[[:space:]]\{0,\}\balias\b/alias/' | sed "s/[\'\"\`]//g")" =~ ^[\ ]*?alias[\ ]*?rm=\/usr\/local\/bin\/new_rm[\ ]*?$ ]]; then
# echo "别名为\`alias rm='/usr/local/bin/new_rm'\`,别名正确"
# echo "rc文件中的rm正确"
# echo "一切正常,不需要初始化"
return
# else
# echo "别名错误"
fi
# 如果别名数量为1,但别名不正确`alias rm='/usr/local/bin/new_rm'`,则需要初始化
if ! [[ "$(grep -v '.*#.*' "$rc_file" | grep 'alias' | sed -E 's/^[[:space:]]\{0,\}\balias\b/alias/' | sed "s/[\'\"\`]//g")" =~ ^[\ ]*?alias[\ ]*?rm=\/usr\/local\/bin\/new_rm[\ ]*?$ ]]; then
# else
# echo "别名不正确,开始自动修改"
# echo "删除$rc_file中的所有关于rm的alias别名定义"
sed -i "/^[[:space:]]\{0,\}\balias\b[[:space:]]\{1,\}[\"\'\`]\?\brm\b[\"\'\`]\?/d" "$rc_file"
# echo "向 $rc_file 中添加一行正确的别名2"
echo "alias rm='/usr/local/bin/new_rm'" >>$rc_file
is_add_alias=1
# 再次初始化
# echo "再次初始化1"
init_new_rm
fi
# 如果/usr/local/bin/new_rm存在,且别名数量 大于 1,则删除所有关于rm的alias别名定义,然后添加一行`alias rm='/usr/local/bin/new_rm'`
elif [ -e /usr/local/bin/new_rm ] && [ $alias_number -gt 1 ]; then
# echo "/usr/local/bin/new_rm存在;关于rm的alias别名总数量 大于 1"
# 删除~/.zshrc或者~/.bashrc中的所有关于rm的alias别名定义
# echo "删除$rc_file中的所有关于rm的alias别名定义"
sed -i '/ *alias *rm=/d' $rc_file
# echo "向 $rc_file 中添加一行正确的别名3"
echo "alias rm='/usr/local/bin/new_rm'" >>$rc_file
# 再次初始化
# echo "再次初始化2"
init_new_rm
fi
# echo "再次初始化"
# echo "is_add_alias=$is_add_alias"
init_new_rm
}
function init_trash_path_and_time_interval() { # 初始化垃圾桶路径和清理垃圾桶的时间间隔
# 如果 crontab 中对应的注释和cron都唯一存在
if [[ $(crontab -l | grep -cE "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"") -eq 1 ]] && [[ $(crontab -l | grep -cE '^0 \*/[0-9]+ \* \* \* \[ -n "\$\(ls -A \/.*?\)" \] && rm -rf \/.*?\/\* \|\| true$') -eq 1 ]]; then # 如果crontab中对应的注释和cron都唯一存在
# 提取crontab中注释行中的垃圾桶路径和cron表达式中的两个垃圾桶路径分别赋值给path0,path1,path2
path0=$(crontab -l | grep -Eo "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"" | sed -E 's/^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:"(.*)"$/\1/g')
path1=$(crontab -l | grep -Eo "0 \*\/[0-9]+ \* \* \* \[ -n \"\\$\\(ls -A (.*?)\\)\" \] && rm -rf (.*?)\/\* \\|\\| true" | sed -E "s/0 \*\/[0-9]+ \* \* \* \[ -n \"\\$\\(ls -A (.*?)\\)\" \] && rm -rf (.*?)\/\* \\|\\| true/\1/g")
path2=$(crontab -l | grep -Eo "0 \*\/[0-9]+ \* \* \* \[ -n \"\\$\\(ls -A (.*?)\\)\" \] && rm -rf (.*?)\/\* \\|\\| true" | sed -E "s/0 \*\/[0-9]+ \* \* \* \[ -n \"\\$\\(ls -A (.*?)\\)\" \] && rm -rf (.*?)\/\* \\|\\| true/\2/g")
# 对比path0和path1,path2是否相等
if [[ $path0 == $path1 ]] && [[ $path0 == $path2 ]]; then # 如果三个路径都相等
# 声明全局变量垃圾桶路径
declare -g TRASH_DIR=$path0
# 获取时间间隔
time0=$(crontab -l | grep -Eo "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"" | sed -E 's/^# 每隔([0-9]+)小时自动清理垃圾桶,当前垃圾桶路径:".*"$/\1/g') # 提取crontab中注释行中的时间间隔
time1=$(crontab -l | grep -Eo "0 \*\/([0-9]+) \* \* \* \[ -n \"\\$\\(ls -A \/.*?\\)\" \] && rm -rf \/.*?\/\* \\|\\| true" | sed -E "s,0 \*/([0-9]+) \* \* \* \[ -n \"\\\$\\(ls -A /.*?\\)\" \] && rm -rf /.*?/\* \|\| true,\1,g") # 提取crontab中cron表达式中的时间间隔
if [[ $time0 != $time1 ]]; then # 如果两个时间间隔不相等
echo "因为两个时间间隔不相等,故重置为默认值!"
init_default_crontab
# 如果两个时间间隔相等,但是不是1-23之间的整数,则重置为默认值
elif [[ $time0 == $time1 ]] && [[ $time0 -lt 1 ]] || [[ $time0 -gt 23 ]]; then # 如果两个时间间隔相等,但是不是1-23之间的整数
echo "因为crontab中的时间间隔不合法,故重置为默认值!"
init_default_crontab
else
# echo "以前已正确设置过垃圾桶路径,跳过路径初始化"
: # 什么都不做
fi
elif [[ $path0 != $path1 ]] || [[ $path0 != $path2 ]]; then # 如果三个路径有一个不相等
echo "虽然crontab 中对应的注释和cron都唯一存在,但是注释行中的垃圾桶路径和cron表达式中的两个垃圾桶路径不一致,故重置为默认值!"
init_default_crontab
fi
# 如果 crontab 中对应的注释和cron不存在
elif [[ $(crontab -l | grep -cE "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"") -eq 0 ]] && [[ $(crontab -l | grep -cE '^0 \*/[0-9]+ \* \* \* \[ -n "\$\(ls -A \/.*?\)" \] && rm -rf \/.*?\/\* \|\| true$') -eq 0 ]]; then # 如果 crontab 中对应的注释和cron不存在
echo "crontab 中对应的注释和cron都不存在,故初始化为默认值!"
init_default_crontab
# 如果 crontab 中对应的注释和cron存在但不唯一
elif [[ $(crontab -l | grep -cE "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"") -gt 1 ]] || [[ $(crontab -l | grep -cE '^0 \*/[0-9]+ \* \* \* \[ -n "\$\(ls -A \/.*?\)" \] && rm -rf \/.*?\/\* \|\| true$') -gt 1 ]]; then # 如果 crontab 中对应的注释和cron存在但不唯一
echo "crontab 中对应的注释和cron存在但不唯一,故重置为默认值!"
init_default_crontab
# 如果 crontab 中对应的注释或者cron只有一个存在
elif [[ $(crontab -l | grep -cE "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"") -eq 1 ]] || [[ $(crontab -l | grep -cE '^0 \*/[0-9]+ \* \* \* \[ -n "\$\(ls -A \/.*?\)" \] && rm -rf \/.*?\/\* \|\| true$') -eq 1 ]]; then # 如果 crontab 中对应的注释或者cron只有一个存在
echo "crontab 中对应的注释或者cron只有一个存在,故重置为默认值!"
init_default_crontab
else
echo -e "未知错误!请手动运行 crontab -e ,并 删除 其中类似于下面的两行,然后重新运行!\n"
echo "# 每隔23小时自动清理垃圾桶,当前垃圾桶路径:\"/home/Trash\",请勿随意修改本行及下一行的内容!一个标点符号都不能动!!否则/usr/local/bin/new_rm会出问题!"
echo "0 */23 * * * [ -n \"\$(ls -A /home/Trash)\" ] && rm -rf /home/Trash/* || true"
exit 1
fi
}
function no_macOS() {
# 如果操作系统是macOS则直接报错退出
if [[ "$OSTYPE" == "darwin"* ]]; then
echo echo "本脚本不支持 macOS 系统,因为 macOS 系统自带的 bash 版本为 3.2.57,不支持 'declare -g' 命令,至少要 4.0 版本的 bash 才支持。"
exit 1
fi
}
# 初始化crontab为默认值
function init_default_crontab() {
# 先删除原来错误的crontab注释和任务
(crontab -l | grep -vE "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"") | crontab -
(crontab -l | grep -vE "0 \*\/[0-9]+ \* \* \* \[ -n \"\\$\\(ls -A \/.*?\\)\" \] && rm -rf \/.*?\/\* \\|\\| true") | crontab -
# 再重置为默认值
(
crontab -l 2>/dev/null
echo "# 每隔23小时自动清理垃圾桶,当前垃圾桶路径:\"/home/Trash\",请勿随意修改本行及下一行的内容!一个标点符号都不能动!!否则/usr/local/bin/new_rm会出问题!"
) | crontab -
(
crontab -l 2>/dev/null
echo "0 */23 * * * [ -n \"\$(ls -A /home/Trash)\" ] && rm -rf /home/Trash/* || true"
) | crontab -
# 声明全局变量默认垃圾桶路径
declare -g TRASH_DIR="/home/Trash"
echo "初始化完成,使用 rm -h 查看帮助"
echo "默认垃圾桶路径为:$TRASH_DIR"
echo "默认每隔23小时清理一次垃圾桶"
}
function update_TRASH_DIR_in_function_set_path() {
TRASH_DIR=$(readlink -f "$1") # 获取绝对路径,防止用户输入的是相对路径
# 更新crontab中的垃圾桶目录和$TRASH_DIR变量
declare -g TRASH_DIR="$TRASH_DIR"
time0=$(crontab -l | grep -Eo "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"" | sed -E 's/^# 每隔([0-9]+)小时自动清理垃圾桶,当前垃圾桶路径:".*"$/\1/g') # 提取crontab中注释行中的时间间隔
# 先删除原来错误的crontab注释和任务
(crontab -l | grep -vE "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"") | crontab -
(crontab -l | grep -vE "0 \*\/[0-9]+ \* \* \* \[ -n \"\\$\\(ls -A \/.*?\\)\" \] && rm -rf \/.*?\/\* \\|\\| true") | crontab -
# 再更新
# (crontab -l 2>/dev/null; echo '# 每隔23小时自动清理垃圾桶,当前垃圾桶路径:"/home/Trash",请勿随意修改本行及下一行的内容!一个标点符号都不能动!!否则/usr/local/bin/new_rm会出问题!') | crontab -
# (crontab -l 2>/dev/null; echo '# 每隔23小时自动清理垃圾桶,当前垃圾桶路径:"/home/Trash",请勿随意修改本行及下一行的内容!一个标点符号都不能动!!否则/usr/local/bin/new_rm会出问题!') | crontab -
(
crontab -l 2>/dev/null
echo "# 每隔$time0小时自动清理垃圾桶,当前垃圾桶路径:\"$TRASH_DIR\",请勿随意修改本行及下一行的内容!一个标点符号都不能动!!否则/usr/local/bin/new_rm会出问题!"
) | crontab -
(
crontab -l 2>/dev/null
echo "0 */$time0 * * * [ -n \"\$(ls -A $TRASH_DIR)\" ] && rm -rf $TRASH_DIR/* || true"
) | crontab -
echo "已将新的垃圾桶路径设置为:$TRASH_DIR"
}
function check_before_rm() {
# 判断根路径/下的所有文件和文件夹(不包含.开头的隐藏文件)组成的数组是否为用户输入的所有参数组成的数组的子集,如果是子集,那么很有可能参数中包含了危险的`^\/\*$`,也就是单独的`/*`
# 这样拐着弯判断的原因是:当你在命令行中输入 * 时,它会被 shell 解释成通配符(wildcard),表示匹配当前目录下的所有文件和子目录。因此,在你运行脚本时,* 已经被 shell 扩展为一个文件列表,而不是原始字符 *。
# 本来应该是先判断:当`/`数量小于2的时候,对比正则表达式`^(?:\.|\/)(?:\.\/)+|.*\/+\.?${root_files_sorted[$i]}$`,但是最后失败了,直接用三种情况来代替吧,如果有人硬是要输入`rm ////////*`或者`rm /./././////.//.*`这种的话,我也没办法了
# 获取根目录下的所有文件和文件夹并排序
root_files_sorted=($(ls -1 / | sort)) # 前面什么都没有的
root_files_sorted_2=($(ls -1 / | sort | sed 's/^/.\//')) # 前面加了个`./`的
root_files_sorted_3=($(ls -1 / | sort | sed 's/^/\//')) # 前面加了个`/`的
# 将用户输入的参数按字母顺序排序并保存到数组 args_sorted 中
args_sorted=($(printf '%s\n' "$@" | sort))
#循环检测args_sorted中的每个元素是否在单独的`/`,则报错并exit 1
for i in "${args_sorted[@]}"; do
if [[ $i == "/" ]]; then
echo "不允许删除根目录!"
exit 1
fi
done
# 定义函数来判断子集
function is_subset() {
local root_files=("$@")
local is_subset=true
# 如果 root_files 数组元素数量大于 args_sorted 数组元素数量,则不可能为子集
if [[ ${#root_files[@]} -gt ${#args_sorted[@]} ]]; then
is_subset=false
else
# 定义指向两个数组的指针
i=0
j=0
# 采用类似于归并排序的方式遍历两个数组
while [[ $i -lt ${#root_files[@]} && $j -lt ${#args_sorted[@]} ]]; do
# 如果 root_files[i] 比 args_sorted[j] 小,那么就需要再比较 root_files[i+1] 是否在 args_sorted 中存在
if [[ ${root_files[$i]} < ${args_sorted[$j]} ]]; then
found=false
for k in $(seq $i ${#root_files[@]}); do
if [[ ${root_files[$k]} == ${args_sorted[$j]} ]]; then
found=true
i=$k
break
fi
done
if [ "$found" == false ]; then
is_subset=false
break
fi
# 如果 root_files[i] 比 args_sorted[j] 大,那么就需要再比较 args_sorted[j+1] 是否在 root_files 中存在
elif [[ ${root_files[$i]} > ${args_sorted[$j]} ]]; then
found=false
for k in $(seq $j ${#args_sorted[@]}); do
if [[ ${args_sorted[$k]} == ${root_files[$i]} ]]; then
found=true
j=$k
break
fi
done
if [ "$found" == false ]; then
is_subset=false
break
fi
# 如果两个元素相等,那么就直接将指针右移一位
else
i=$((i + 1))
j=$((j + 1))
fi
done
fi
echo $is_subset
}
# 判断三种情况下根目录是否为用户输入参数的子集
is_subset_1=$(is_subset "${root_files_sorted[@]}")
is_subset_2=$(is_subset "${root_files_sorted_2[@]}")
is_subset_3=$(is_subset "${root_files_sorted_3[@]}")
# 输出结果
# echo "is_subset_1= $is_subset_1"
# echo "is_subset_2= $is_subset_2"
# echo "is_subset_3= $is_subset_3"
if [[ $is_subset_1 == true || $is_subset_2 == true || $is_subset_3 == true ]]; then
# echo "根目录是用户输入参数的子集"
is_root_files_subset_args=1
else
# echo "根目录不是用户输入参数的子集"
is_root_files_subset_args=0
fi
# echo "root_files_sorted= ${root_files_sorted[*]}"
# echo " args_sorted= ${args_sorted[*]}"
# echo "is_root_files_subset_args= $is_root_files_subset_args"
if [[ $is_root_files_subset_args == 1 ]]; then
read -p "你有可能正在删除根目录中的所有文件!!!!!!!确定要继续吗? [y/n]" choice
case "$choice" in
y | Y)
# 用户选择继续执行,执行原始命令并以其返回码退出
;;
*)
# 其他任何输入,中止
echo "用户已取消操作。"
exit 1
;;
esac
fi
}
# 初始化成功以后检查当前运行的是不是/usr/local/bin/new_rm,如果是,则退出当前函数,否则,删除当前运行的脚本
function self_eraser() {
# 如果当前运行的是/usr/local/bin/new_rm,则退出当前函数
if [[ "$0" == "/usr/local/bin/new_rm" ]]; then
return
fi
# 如果当前运行的不是/usr/local/bin/new_rm,则删除当前运行的脚本
if [[ "$0" != "/usr/local/bin/new_rm" ]]; then
rm -f $0
fi
}
# 一键卸载本脚本的函数
function uninstall_new_rm() {
#首先再次向用户询问是否卸载
read -p "确定要卸载本脚本吗?[y/N]: " answer
if [[ "$answer" == "y" || "$answer" == "Y" ]]; then # 如果用户选择卸载
# 如果回收站不为空,则询问用户是否清空回收站
if [[ -n "$(ls -A $TRASH_DIR)" ]]; then # 如果回收站不为空
# echo "垃圾桶中所有文件:"
# echo -e '大小 "删除时间 (文件类型:文件名)"\n'
# du -sh "${TRASH_DIR}"/* | sed "s|${TRASH_DIR}/||"| sed 's/\(.*\)\t\(.*\)/\1\t\"\2\"/'
echo -e "以下是垃圾桶目录中的所有文件:\n"
# files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ printf "%-10s%-10s\"%s\"\n", NR, $1, substr($0, index($0, $2)) }')
files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ size=$1"B"; path=substr($0, index($0, $2)); printf "%-10s%-10s\"%s\"\n", NR, size, path }')
echo -e "编号 大小 \"删除时间 (文件类型:文件名)\"\n${files}"
echo -e "在卸载本脚本前,是否清空回收站?\n如果选择 [y],则会直接清空回收站,然后卸载本脚本\n如果选择 [n],则会在不清空回收站的情况下,直接卸载本脚本\n如果选择 [c],则会取消卸载"
read -p "请选择 [y/N/c]: " answer
if [[ "$answer" == "y" || "$answer" == "Y" ]]; then # 如果用户选择清空回收站
# 清空垃圾桶目录
rm -rf "${TRASH_DIR:?}/"*
echo "已成功 清空 垃圾桶:${TRASH_DIR}"
elif [[ "$answer" == "n" || "$answer" == "N" ]]; then # 如果用户选择不清空回收站
echo "已取消清空回收站,将在保留回收站中的文件的情况下卸载本脚本"
echo "回收站的路径为:${TRASH_DIR} 请在卸载完成后自行处理"
elif [[ "$answer" == "c" || "$answer" == "C" ]]; then # 如果用户选择取消卸载
echo "已取消卸载"
exit 0
else
echo "输入错误,已取消卸载"
exit 1
fi
fi
else # 如果用户选择不卸载,只要不是y或者Y,都视为不卸载
echo "已取消卸载"
exit 0
fi
#正式开始卸载
# 如果~/.zshrc或者~/.bashrc中存在关于rm的alias别名定义,则删除它
if [[ "$(grep -v '.*#.*' "$rc_file" | grep 'alias' | sed -E 's/^[[:space:]]\{0,\}\balias\b/alias/' | sed "s/[\'\"\`]//g")" =~ ^[\ ]*?alias[\ ]*?rm=\/usr\/local\/bin\/new_rm[\ ]*?$ ]]; then
sed -i "/^[[:space:]]\{0,\}\balias\b[[:space:]]\{1,\}[\"\'\`]\?\brm\b[\"\'\`]\?/d" "$rc_file"
fi
#删除crontab中的相关任务和注释
(crontab -l | grep -vE "0 \*\/[0-9]+ \* \* \* \[ -n \"\\$\\(ls -A \/.*?\\)\" \] && rm -rf \/.*?\/\* \\|\\| true") | crontab -
(crontab -l | grep -vE "# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\"\/.*?\",请勿随意修改本行及下一行的内容!一个标点符号都不能动!!否则\/usr\/bin\/rm_new会出问题!") | crontab -
# 删除/usr/local/bin/new_rm
rm -rf "/usr/local/bin/new_rm"
#检查一下,如果/usr/local/bin/new_rm不存在,并且~/.zshrc或者~/.bashrc中不存在关于rm的alias别名定义,则说明卸载成功
if [[ ! -e /usr/local/bin/new_rm ]] && [[ ! "$(grep -v '.*#.*' "$rc_file" | grep 'alias' | sed -E 's/^[[:space:]]\{0,\}\balias\b/alias/' | sed "s/[\'\"\`]//g")" =~ ^[\ ]*?alias[\ ]*?rm=\/usr\/local\/bin\/new_rm[\ ]*?$ ]]; then
echo "已彻底卸载本脚本"
else
echo "不知道为什么卸载失败了"
echo "请手动删除/usr/local/bin/new_rm"
echo "并且删除~/.zshrc或者~/.bashrc中的关于rm的alias别名定义"
echo “还有删除crontab中的相关任务和注释”
echo "然后重启终端"
fi
$shell
}
# 这个函数的功能是检测参数是否符合要求,如果不符合要求则退出脚本
# 检测传入的参数是否合法
function parse_args() {
local args=("$@") # 将所有参数存入 args 数组中
# 定义合法的参数列表
legal_args_1_dash_list=("-config" "-cron" "-del" "-e" "-f" "-rf" "-h" "-ls" "-l" "-ra" "-re" "-set" "-uninstall" "-update" "-v")
legal_args_2_dash_list=("--configeration" "--crontab" "--delete" "--empty" "--force" "--help" "--list" "--restore" "--restore-all" "--set-path" "--uninstall" "--update" "--version")
# 遍历所有参数,- 开头字符串存入 param_1_dash 数组中,-- 开头字符串存入 param_2_dash 数组中,其他字符串存入 param_others 数组中
for param in "${args[@]}"; do
# 如果当前参数的开头为 --
if [[ "$param" == --* ]]; then
param_2_dash+=("${param:2}")
# 如果当前参数的开头为 -
elif [[ "$param" == -* ]]; then
param_1_dash+=("${param:1}")
# 如果当前参数不以 - 或 -- 开头,则直接跳过
else
param_others+=("$param")
fi
done
# echo "param_1_dash: ${param_1_dash[@]}" # 打印出所有以 - 开头的参数
# echo "param_2_dash: ${param_2_dash[@]}" # 打印出所有以 -- 开头的参数
# echo "param_others: ${param_others[@]}" # 打印出所有不以 - 或 -- 开头的参数
# 遍历所有以 - 开头的参数,如果参数不在合法参数列表中,则报错
for param in "${param_1_dash[@]}"; do
if ! [[ "${legal_args_1_dash_list[@]}" =~ "$param" ]]; then
echo "无效的选项 -$param "
exit 1
fi
done
# 遍历所有以 -- 开头的参数,如果参数不在合法参数列表中,则报错
for param in "${param_2_dash[@]}"; do
if ! [[ "${legal_args_2_dash_list[@]}" =~ "$param" ]]; then
echo "无效的选项 --$param 将被忽略"
exit 1
fi
done
#然后判断传入的参数的数量,如果只有一个,则判断这个参数是不是以`-`开头,如果不是,则直接return跳过检查函数,说明这个命令是合法的,如`rm /path/to/file`这种命令
if [[ $# -eq 1 ]]; then
if ! [[ "${1:0:1}" == "-" ]]; then
return # 如果不是以`-`开头,则直接return,就是说`rm /path/to/file`这种命令是合法的
fi
fi
# 如果传入的参数的数量大于1,则遍历所有的参数,如果所有参数中,有大于等于两个`-`开头的参数,则直接exit 1报错
if [[ $# -gt 1 ]]; then
local count=0
for arg in "${args[@]}"; do
if [[ "${arg:0:1}" == "-" ]]; then
((count++))
fi
done
if [[ $count -ge 2 ]]; then
echo "多个选项不能同时使用"
exit 1
fi
fi
# 下面开始分别讨论每个参数的合法性
case "$1" in
# -config --configeration, 后面不能跟参数,如果跟了参数,则直接exit 1报错
"-config" | "--configuration")
if [[ $# -gt 1 ]]; then
echo "错误:-config --configuration 选项不需要参数"
exit 1
fi
;;
# -cron --crontab, 有2种情况,非交互式和交互式,非交互式的情况下,后面必须有一个数字的参数,如果没有,则直接exit 1报错;如果后面没有参数,则直接return跳过检查函数进入交互模式,说明这个命令是合法的,如`rm -cron`这种命令
"-cron" | "--crontab")
if [[ $# -eq 1 ]]; then
# 没有参数,进入交互模式
echo "进入交互模式"
return 0
elif [[ $# -eq 2 && "$2" =~ ^[0-9]+$ ]]; then
# 有一个数字参数
echo "检测到数字参数 $2,进入非交互模式"
return 0
elif [[ $# -ge 3 ]]; then
# 参数数量大于等于3
echo "错误:不支持多余参数"
return 1
fi
;;
# -del --delete后面不能有任何参数,如果有,则直接exit 1报错
"-del" | "--delete")
if [[ $# -gt 1 ]]; then
echo "错误:-del --delete 选项不需要参数"
exit 1
fi
;;
# -e --empty, 后面不能跟参数,如果跟了参数,则直接exit 1报错
"-e" | "--empty")
if [[ $# -gt 1 ]]; then
echo "错误:-e --empty 选项不需要参数"
exit 1
fi
;;
# -f --force, 后面必须要有一个非`-`开头的参数表示要删除的文件或文件夹,如果没有,则直接exit 1报错
# 这个在对应的函数中已经判断过了,这里不需要再判断了
"-f" | "--force") ;;
# -h --help, 后面不能跟参数,如果跟了参数,则直接exit 1报错
"-h" | "--help")
if [[ $# -gt 1 ]]; then
echo "错误:-h --help 选项不需要参数"
exit 1
fi
;;
# -l -ls --list, 后面不能跟参数,如果跟了参数,则直接exit 1报错
"-l" | "-ls" | "--list")
if [[ $# -gt 1 ]]; then
echo "错误:-l -ls --list 选项不需要参数"
exit 1
fi
;;
# -ra --restore-all, 后面不能跟参数,如果跟了参数,则直接exit 1报错
"-ra" | "--restore-all")
if [[ $# -gt 1 ]]; then
echo "错误:-ra --restore-all 选项不需要参数"
exit 1
fi
;;
# -re --restore, 后面不能跟参数,如果跟了参数,则直接exit 1报错
"-re" | "--restore")
if [[ $# -gt 1 ]]; then
echo "错误:-re --restore 选项不需要参数"
exit 1
fi
;;
# -set --set-path,有2种情况,非交互式和交互式,非交互式的情况下,后面必须只能有一个参数,否则直接exit 1报错;如果后面没有参数,则直接return跳过检查函数进入交互模式,说明这个命令是合法的,如`rm -set`这种命令
"-set" | "--set-path")
if [[ $# -eq 1 ]]; then
# 没有参数,进入交互模式
return 0
elif [[ $# -eq 2 ]]; then
# 有一个参数
# echo "检测到参数 $2,进入非交互模式"
return 0
elif [[ $# -ge 3 ]]; then
# 参数数量大于等于3
echo "错误:不支持多余参数"
return 1
fi
;;
# -uninstall --uninstall, 后面不能跟参数,如果跟了参数,则直接exit 1报错
"-uninstall" | "--uninstall")
if [[ $# -gt 1 ]]; then
echo "错误:-uninstall --uninstall 选项不需要参数"
exit 1
fi
;;
# -v --version, 后面不能跟参数,如果跟了参数,则直接exit 1报错
"-v" | "--version")
if [[ $# -gt 1 ]]; then
echo "错误:-v --version 选项不需要参数"
exit 1
fi
;;
"-update" | "--update")
if [[ $# -gt 1 ]]; then
echo "错误:-update --update 选项不需要参数"
exit 1
fi
;;
esac
}
# 恢复函数
function restore() {
function restore_single_file() {
local file="$1"
local source_path target_path temp_target_path
local answer
if [[ -d "$TRASH_DIR/$file" || -f "$TRASH_DIR/$file" ]]; then
# 读取对应的info文件,获取源路径和目标路径
source_path=$(sed -n '2p' "${TRASH_DIR}/${file}/info" | tr -d "'")
temp_target_path=$(sed -n '1p' "${TRASH_DIR}/${file}/info" | tr -d "'")
target_path="${temp_target_path}/$(basename "$source_path")"
real_source_path="$temp_target_path/$(echo "$file" | sed -E 's/.*(文件|文件夹):(.*)\)/\2/')" # 获取真实的源路径
if [[ -e "$target_path" ]]; then
# 如果目标路径存在且与源路径不同,则触发覆盖询问
while true; do
if [[ "$GLOBAL_ANSWER" == "a" ]]; then
answer="y"
elif [[ "$GLOBAL_ANSWER" == "s" ]]; then
answer="n"
else
echo "目标路径已经存在同名文件 \"${real_source_path}\" ,是否覆盖恢复?[y (覆盖)/n (跳过)/q (退出)/a (全部同意)/s (全部跳过)]"
read answer
fi
case $answer in
y | Y)
# 如果用户确认覆盖,则将源路径的文件移动到目标路径
if [[ -f "$source_path" ]]; then
mv "$source_path" "$temp_target_path"
elif [[ -d "$source_path" ]]; then
cp -rf "$source_path" "$temp_target_path"
fi
rm -rf "$TRASH_DIR/$file"
echo "已成功恢复 \"$file\" 到 \"$real_source_path\""
break
;;
n | N)
# 如果用户拒绝覆盖,则输出消息并退出循环
echo "跳过恢复 \"$file\""
break
;;
q | Q)
# 如果用户选择退出,则直接退出脚本
echo "恢复操作已取消"
exit 0
;;
a | A)
# 如果用户选择全部同意,则将GLOBAL_ANSWER变量设置为"a",并继续执行后面的步骤
GLOBAL_ANSWER="a"
;;
s | S)
# 如果用户选择全部跳过,则将GLOBAL_ANSWER变量设置为"s",并直接返回
GLOBAL_ANSWER="s"
break
;;
*)
# 如果用户输入了非法的选项,则提示用户重新输入
echo "请输入有效的选项![y (覆盖)/n (跳过)/q (退出)/a (全部同意)/s (全部跳过)]"
;;
esac
done
else
# 如果目标路径不存在或者与源路径相同,则直接移动源路径的文件到目标路径
if [[ -f "$source_path" ]]; then
mv "$source_path" "$temp_target_path"
elif [[ -d "$source_path" ]]; then
cp -rf "$source_path" "$temp_target_path"
fi
rm -rf "$TRASH_DIR/$file"
echo "已成功恢复 \"$file\" 到 \"$real_source_path\""
fi
else
echo "$file 不是一个有效的文件或目录1"
fi
}
# restore主函数:
# 判断垃圾桶目录是否为空
if [ "$(ls -A ${TRASH_DIR})" ]; then # 如果不为空
declare -g is_restore_all=0 # 默认不是恢复所有文件
if [[ "$is_restore_all" == 1 ]]; then # 判断 is_restore_all 是否为真,如果为True的话,说明是-ra,需要恢复所有文件
# 获取所有被删除的文件列表
files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ size=$1"B"; path=substr($0, index($0, $2)); printf "%-10s%-10s\"%s\"\n", NR, size, path }')
# 遍历所有文件进行恢复
for num in $(seq 1 "$(echo "$files" | wc -l)"); do
# 获取当前文件对应的文件名
file_name=$(echo "${files}" | sed -n "${num}p" | awk -F '"' '{print $2}')
restore_single_file "$file_name"
done
elif [[ "$is_restore_all" == 0 ]]; then # # 否则 is_restore_all 为假,说明是-re,需要恢复指定文件
# 列出所有被删除的文件,并且文件开头需要有编号1-n,然后用户输入要恢复的文件编号
echo -e "以下是垃圾桶目录中的所有文件:\n"
# files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ printf "%-10s%-10s\"%s\"\n", NR, $1, substr($0, index($0, $2)) }')
files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ size=$1"B"; path=substr($0, index($0, $2)); printf "%-10s%-10s\"%s\"\n", NR, size, path }')
echo -e "编号 大小 \"删除时间 (文件类型:文件名)\"\n${files}"
read -p "请输入要恢复的文件编号(多个文件用空格隔开):" res_files
# 判断用户输入的文件编号是否合法
if [[ "$res_files" =~ ^[0-9]+(\ [0-9]+)*$ ]]; then
for res_file in $(echo "${res_files}"); do
# 将用户输入的文件编号转换为文件名
file_name=$(echo "${files}" | sed -n "${res_file}p" | awk -F '"' '{print $2}')
restore_single_file "$file_name"
done
fi
else
echo "参数错误:文件编号只能是数字"
fi
else
echo "垃圾桶目录为空"
fi
}
# 执行脚本
no_macOS # 检查是否是macOS系统
CURRENT_TIME=$(date "+%Y-%m-%d-%H:%M:%S") # 定义 CURRENT_TIME 变量
declare -g is_add_alias=0 # 设置一个全局变量来表示是否添加过别名
init_new_rm # 初始化新的rm命令
self_eraser # 自我删除
parse_args "$@" # 解析参数是否合法
GLOBAL_ANSWER="" # 全局变量,用于存储用户对于确认操作的选择
# 处理命令行参数
case "$1" in
-h | --help) # 帮助信息
usage
exit 0
;;
-re | --restore) # 恢复文件
restore
;;
-f | --force | -rf) # 强制删除
check_before_rm "$@"
# 去掉命令行参数中的 -f 参数,并执行真正的删除操作
shift
# 定义一个数组,用于存储不存在的文件
not_exist_files=()
for file in "$@"; do
if [[ ! -e "$file" ]]; then
# 将不存在的文件添加到数组中
not_exist_files+=("$file")
echo "错误:文件或目录 '$file' 不存在"
fi
done
# 如果存在不存在的文件则输出数组,并退出程序
if [[ ${#not_exist_files[@]} -gt 0 ]]; then
echo "请再次检查对应的文件或目录是否存在!"
exit 1
fi
if [[ "$#" -gt 0 ]]; then
read -p "确定 永久删除 这些文件? $(echo "$@" | sed 's/\(-f\|--force\|-rf\) //')。注意:删除后将无法恢复! [y/N]: " answer
if [[ "$answer" == "y" || "$answer" == "Y" ]]; then
rm -rf "$@"
echo "已成功永久删除指定的文件或目录"
else
echo "删除操作已取消"
exit 1
fi
else
echo "没有指定要删除的文件或目录"
fi
;;
-l | -ls | --list) # 列出垃圾桶目录中的所有文件
# 判断垃圾桶目录是否为空
if [ "$(ls -A ${TRASH_DIR})" ]; then
# 如果不为空,列出所有被删除的文件,并且文件开头需要有编号1-n
echo -e "以下是垃圾桶目录中的所有文件:\n"
# files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ printf "%-10s%-10s\"%s\"\n", NR, $1, substr($0, index($0, $2)) }')
files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ size=$1"B"; path=substr($0, index($0, $2)); printf "%-10s%-10s\"%s\"\n", NR, size, path }')
echo -e "编号 大小 \"删除时间 (文件类型:文件名)\"\n${files}"
else
echo "垃圾桶目录为空"
fi
;;
-e | --empty) # 清空垃圾桶
# 清空垃圾桶目录
# echo "垃圾桶中所有文件:"
# echo -e '大小 "删除时间 (文件类型:文件名)"\n'
# du -sh "${TRASH_DIR}"/* | sed "s|${TRASH_DIR}/||"| sed 's/\(.*\)\t\(.*\)/\1\t\"\2\"/'
echo -e "以下是垃圾桶目录中的所有文件:\n"
# files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ printf "%-10s%-10s\"%s\"\n", NR, $1, substr($0, index($0, $2)) }')
files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ size=$1"B"; path=substr($0, index($0, $2)); printf "%-10s%-10s\"%s\"\n", NR, size, path }')
echo -e "编号 大小 \"删除时间 (文件类型:文件名)\"\n${files}"
read -p "确定 清空 垃圾桶? [y/N]: " answer
if [[ "$answer" == "y" || "$answer" == "Y" ]]; then
rm -rf "${TRASH_DIR:?}/"*
echo "已成功 清空 垃圾桶:${TRASH_DIR}"
else
echo "清空 垃圾桶操作已取消"
exit 1
fi
;;
-config | --configeration) # 查看垃圾桶配置
echo "new_rm: v${VERSION}"
echo "垃圾桶目录:${TRASH_DIR}"
echo "垃圾桶自动清空的时间:$(crontab -l | grep -Eo "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"" | sed -E 's/^# 每隔([0-9]+)小时自动清理垃圾桶,当前垃圾桶路径:".*"$/\1/g') 小时"
;;
-update | --update) # 更新脚本
wget -O remove.sh https://git.dreamdusk.com/Dean/new_rm/raw/branch/main/new_rm.sh && chmod +x remove.sh && ./remove.sh
;;
-set | --set-path) # 设置垃圾桶目录
# 如果 -set 后面有参数,则进入非交互模式
if [[ -n "$2" ]]; then
# 如果原垃圾桶非空,则提示用户是否将原垃圾桶中的所有文件移动到新的垃圾桶目录
if [ "$(ls -A ${TRASH_DIR})" ] && [ "$TRASH_DIR" != "$2" ]; then
echo "警告:您正在将当前的垃圾桶目录 $TRASH_DIR 更改为 $2,原垃圾桶已包含以下文件:"
echo -e '大小 "删除时间 (文件类型:文件名)"\n'
du -sh "${TRASH_DIR}"/* | sed "s|${TRASH_DIR}/||" | sed 's/\(.*\)\t\(.*\)/\1\t\"\2\"/'
echo -e "\n确定要将原垃圾桶中的所有文件移动到新的垃圾桶目录吗?\n如果选择 [y],则会将原垃圾桶中的所有文件移动到新的垃圾桶目录\n如果选择 [n],则会在不移动原垃圾桶中的文件的情况下,直接将垃圾桶目录更改为新的垃圾桶目录\n如果选择 [c],则会取消更改垃圾桶目录的操作"
read -p "请输入 [y/N/c\]: " answer
if [[ "$answer" == "y" || "$answer" == "Y" ]]; then # 移动原垃圾桶中的所有文件到新的垃圾桶目录,然后将垃圾桶目录更改为新的垃圾桶目录
if [ ! -d "$2" ]; then
echo "警告:新垃圾桶目录 $2 不存在,将自动创建"
mkdir -p "$2"
fi
mv "${TRASH_DIR:?}/"* "$2/"
echo "已成功 迁移 原垃圾桶中的所有文件到:$2"
# 删除旧的垃圾桶目录
rm -r "${TRASH_DIR:?}"
echo "已删除旧的垃圾桶目录:$TRASH_DIR"
update_TRASH_DIR_in_function_set_path "$2" # 更新函数中的垃圾桶目录
elif [[ "$answer" == "n" || "$answer" == "N" ]]; then # 不移动原垃圾桶中的所有文件到新的垃圾桶目录,然后将垃圾桶目录更改为新的垃圾桶目录
# 显示取消消息
echo "迁移操作已取消"
# 不移动原垃圾桶中的所有文件到新的垃圾桶目录
echo "未移动任何文件到新的垃圾桶目录"
if [ ! -d "$2" ]; then
echo "警告:垃圾桶目录 $2 不存在,将自动创建"
mkdir -p "$2"
fi
update_TRASH_DIR_in_function_set_path "$2" # 更新函数中的垃圾桶目录
# 提醒用户新的垃圾桶目录已经设置过,并且原垃圾桶及其中的垃圾还在,请及时清理
echo "警告:虽然您已经设置了新的垃圾桶目录 $2,但是原垃圾桶 $TRASH_DIR 和其中的垃圾还在,请及时清理。"
elif [[ "$answer" == "c" || "$answer" == "C" ]]; then # 取消更改垃圾桶目录的操作
echo "设置操作已取消"
exit 0
else
echo "输入不合法,已取消操作"
exit 1
fi
# 如果原垃圾桶为空,则直接更新垃圾桶路径
else
# 如果新垃圾桶目录不存在,则自动创建
if [ ! -d "$2" ]; then
echo "警告:垃圾桶目录 $2 不存在,将自动创建"
mkdir -p "$2"
fi
update_TRASH_DIR_in_function_set_path "$2" # 更新函数中的垃圾桶目录
fi
# 如果没有指定 -set 参数,则进入交互式设置垃圾桶路径
else
# 交互式设置垃圾桶路径
# 先保存当前垃圾桶的路径
CURRENT_TRASH_DIR=$TRASH_DIR
# 再告知用户当前垃圾桶的路径
echo "当前垃圾桶路径为:$CURRENT_TRASH_DIR"
read -p "请输入新的垃圾桶路径:" TRASH_DIR
# 如果原垃圾桶非空,则提示用户是否将原垃圾桶中的所有文件移动到新的垃圾桶目录
if [[ -d "${CURRENT_TRASH_DIR}" ]] && [ "$(ls -A ${CURRENT_TRASH_DIR})" ] && [ "$CURRENT_TRASH_DIR" != "$TRASH_DIR" ]; then
echo "警告:您正在将当前的垃圾桶目录 $CURRENT_TRASH_DIR 更改为 $TRASH_DIR,该原垃圾桶已包含以下文件:"
echo -e '大小 "删除时间 (文件类型:文件名)"\n'
du -sh "${CURRENT_TRASH_DIR}"/* | sed "s|${CURRENT_TRASH_DIR}/||" | sed 's/\(.*\)\t\(.*\)/\1\t\"\2\"/'
# read -p "确定要将原垃圾桶中的所有文件移动到新的垃圾桶目录吗? [y/N/c]: " answer
echo -e "\n确定要将原垃圾桶中的所有文件移动到新的垃圾桶目录吗?\n如果选择 [y],则会将原垃圾桶中的所有文件移动到新的垃圾桶目录\n如果选择 [n],则会在不移动原垃圾桶中的文件的情况下,直接将垃圾桶目录更改为新的垃圾桶目录\n如果选择 [c],则会取消更改垃圾桶目录的操作"
read -p "请输入 [y/N/c\]: " answer
if [[ "$answer" == "y" || "$answer" == "Y" ]]; then
# 如果垃圾桶目录不存在,则创建
if [ ! -d "$TRASH_DIR" ]; then
echo "警告:垃圾桶目录 $TRASH_DIR 不存在,将自动创建"
mkdir -p "$TRASH_DIR"
fi
# 移动原垃圾桶中的所有文件到新的垃圾桶目录
mv "${CURRENT_TRASH_DIR:?}/"* "$TRASH_DIR/"
echo "已成功 迁移 原垃圾桶中的所有文件到:$TRASH_DIR"
# 删除旧的垃圾桶目录
rm -r "${CURRENT_TRASH_DIR:?}"
echo "已删除旧的垃圾桶目录:$CURRENT_TRASH_DIR"
update_TRASH_DIR_in_function_set_path "$TRASH_DIR" # 更新函数set_path中的垃圾桶目录
elif [[ "$answer" == "n" || "$answer" == "N" ]]; then
# 显示取消消息
echo "迁移操作已取消"
# 不移动原垃圾桶中的所有文件到新的垃圾桶目录
echo "未移动任何文件到新的垃圾桶目录"
# 如果垃圾桶目录不存在,则创建
if [ ! -d "$TRASH_DIR" ]; then
echo "警告:垃圾桶目录 $TRASH_DIR 不存在,将自动创建"
mkdir -p "$TRASH_DIR"
fi
update_TRASH_DIR_in_function_set_path "$TRASH_DIR" # 更新函数set_path中的垃圾桶目录
# 提醒用户新的垃圾桶目录已经设置过,并且原垃圾桶及其中的垃圾还在,请及时清理
echo "警告:虽然您已经设置了新的垃圾桶目录 $TRASH_DIR,但是原垃圾桶 $CURRENT_TRASH_DIR 和其中的垃圾还在,请及时清理。"
elif [[ "$answer" == "c" || "$answer" == "C" ]]; then
echo "设置操作已取消"
exit 0
else
echo "输入不合法,已取消操作"
exit 1
fi
# 如果原垃圾桶为空,则直接更新垃圾桶路径
else
# 如果垃圾桶目录不存在,则创建
if [ ! -d "$TRASH_DIR" ]; then
echo "警告:垃圾桶目录 $TRASH_DIR 不存在,将自动创建"
mkdir -p "$TRASH_DIR"
fi
update_TRASH_DIR_in_function_set_path "$TRASH_DIR" # 更新函数set_path中的垃圾桶目录
fi
fi
;;
-cron | --crontab) # 设置定时清理垃圾桶的时间间隔
# 如果用户输入的参数不是 `-cron` 或 `-cron x`(x 为 1~23 之间的正整数),则报错
if [[ "$#" -ne 1 && "$#" -ne 2 ]] || [[ "$#" -eq 1 && ("$1" != "-cron") ]] || [[ "$#" -eq 2 && ("$1" != "-cron" || ! "$2" =~ ^([1-9]|1[0-9]|2[0-3])$) ]]; then
echo "参数错误:参数只能是 -cron 或 -cron x(x 为 1~23 之间的正整数)"
exit 1
fi
# 如果 -cron 后面是 1~23 之间的正整数,则进入非交互式
if [[ "$2" =~ ^(1[0-9]|[1-9]|2[0-3])$ ]]; then
interval="$2"
# 如果 -cron 后面没有数字参数,则进入交互式
else
read -p "请输入一个时间间隔(以小时为单位):" interval
# 如果用户输入的不是 1~23 之间的正整数,则让用户重新输入
while [[ ! "$interval" =~ ^[0-9]+$ ]] || [[ "$interval" -eq 0 ]]; do
echo "参数错误:时间间隔取值范围是1~23时间的整数"
read -p "请输入一个时间间隔(以小时为单位):" interval
done
fi
# 获取原来 crontab 中自动清空回收站的时间间隔
current_interval=$(crontab -l | grep -m 1 -w "$command" | awk -F '[^0-9]*' '{print $2}')
# echo "原来 $current_interval 小时清空一次回收站"
# 将时间间隔转换为 crontab 表达式
if [[ "$interval" =~ ^(1[0-9]|[1-9]|2[0-3])$ ]]; then
expression=$(echo "0 */$interval * * *") # 每隔指定小时执行一次
# 定义任务命令和注释行
comment="# 每隔${interval}小时自动清理垃圾桶,当前垃圾桶路径:\"${TRASH_DIR:?}\",请勿随意修改本行及下一行的内容!一个标点符号都不能动!!否则rm_new会出问题!"
command="[ -n \"\$(ls -A ${TRASH_DIR:?})\" ] && rm -rf ${TRASH_DIR:?}/* || true"
# 添加或更新任务并添加注释行和提示信息
pattern="$command"
task="$expression $command"
# 如果 crontab 中已经存在自动清理任务,则更新任务
if crontab -l | grep -q "$pattern"; then
(crontab -l | sed "s|$pattern.*|$comment\n$task|; /^$/d; \$a\\") | crontab -
echo -e "成功修改自动清理任务的时间:\n已由 $current_interval 小时清空一次回收站 修改为 ${interval} 小时清空一次回收站 ${TRASH_DIR:?}"
# 如果 crontab 中不存在自动清理任务,则添加任务
else
(echo -e "$comment\n$task") | crontab -
echo -e "成功修改自动清理任务的时间:\n已由 $current_interval 小时清空一次回收站 修改为 ${interval} 小时清空一次回收站 ${TRASH_DIR:?}"
fi
else
echo "错误:必须提供一个整数时间间隔(以小时为单位)"
fi
;;
# elif [[ "$1" == "-del" ]] || [[ "$1" == "--delete" ]]; then
-del | --delete)
# 判断垃圾桶目录是否为空
if [ "$(ls -A ${TRASH_DIR})" ]; then
# 如果不为空,列出所有被删除的文件,并且文件开头需要有编号1-n,然后用户输入要删除的文件编号
echo -e "以下是垃圾桶目录中的所有文件:\n"
# files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ printf "%-10s%-10s\"%s\"\n", NR, $1, substr($0, index($0, $2)) }')
files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ size=$1"B"; path=substr($0, index($0, $2)); printf "%-10s%-10s\"%s\"\n", NR, size, path }')
echo -e "编号 大小 \"删除时间 (文件类型:文件名)\"\n${files}"
read -p "请输入要永久删除的文件编号(多个文件用空格隔开):" del_files
# 判断用户输入的文件编号是否合法
if [[ "$del_files" =~ ^[0-9]+(\ [0-9]+)*$ ]]; then
for del_file in $(echo "${del_files}"); do
# 将用户输入的文件编号转换为文件名
file_name=$(echo "${files}" | sed -n "${del_file}p" | awk -F '"' '{print $2}')
# 删除用户输入的文件
rm -rf "${TRASH_DIR}/${file_name}"
echo "已永久删除文件:\"${file_name}\""
done
else
echo "参数错误:文件编号只能是数字"
fi
else
echo "垃圾桶目录为空"
fi
;;
-uninstall | --uninstall) # 卸载rm_new
uninstall_new_rm
;;
# # 处理 "-ra" 参数
# elif [[ "$1" == "-ra" ]] || [[ "$1" == "--restore-all" ]]; then
-ra | --restore-all) # 恢复垃圾桶中的所有文件
# 判断垃圾桶目录是否为空
if [ "$(ls -A ${TRASH_DIR})" ]; then
# 如果不为空,列出所有被删除的文件
echo -e "以下是垃圾桶目录中的所有文件:\n"
# files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ printf "%-10s%-10s\"%s\"\n", NR, $1, substr($0, index($0, $2)) }')
files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ size=$1"B"; path=substr($0, index($0, $2)); printf "%-10s%-10s\"%s\"\n", NR, size, path }')
echo -e "编号 大小 \"删除时间 (文件类型:文件名)\"\n${files}"
# 询问用户是否确认恢复所有文件
read -p "确定 恢复 垃圾桶目录中的所有文件?[y/N]" answer
if [[ "$answer" == "y" || "$answer" == "Y" ]]; then
for file in "${TRASH_DIR}"/*; do
# 设置一个全局变量,标志告诉restore函数是否是恢复所有文件
declare -g is_restore_all=1
restore "$(basename "$file")"
done
else
declare -g is_restore_all=0
echo "恢复操作已取消"
exit 1
fi
else
# 如果为空,输出提示信息并退出程序
echo "垃圾桶目录为空,无法恢复任何文件"
exit 1
fi
;;
# 处理 "-v" "--version" 参数
-v | --version)
echo "new_rm: v${VERSION}"
;;
*) # 如果以上参数都不匹配,就说明用户需要的是直接删除文件
#更新脚本的时候就不用报错"rm: 缺少参数"了,检测方法是:1.如果is_add_alias=0,说明脚本没有修改过rc_file添加别名;2.如果当前脚本是"/usr/local/bin/new_rm";结合 1&&2==true 就能判断出不是更新脚本
if [[ "$0" = "/usr/local/bin/new_rm" ]] && [[ "$is_add_alias" -eq 0 ]]; then
# 如果没有任何参数,则直接exit 1报错
if [[ $# -eq 0 ]]; then
echo "rm: 缺少参数"
echo "请尝试使用 'rm --help' 以获取更多信息。"
fi
fi
# 调用 check_before_rm 检查
check_before_rm "$@"
# 遍历所有命令行参数
for arg in "$@"; do
# 判断当前参数是否为一个存在的文件(夹)
if [[ -e "$arg" ]]; then
# 获取要删除的文件或目录的绝对路径和目录路径
file_path="$(readlink -f "$arg")"
dir_path="$(dirname "$file_path")"
# 获取文件或目录名
if [[ -d $file_path ]]; then
file_type="文件夹"
else
file_type="文件"
fi
file_name="$(basename "$file_path")"
# 在垃圾桶目录中创建与文件名相同的子目录,并将文件或目录移动到该子目录下
mkdir -p "${TRASH_DIR}/${CURRENT_TIME} (${file_type}:${file_name})"
mv "$file_path" "${TRASH_DIR}/${CURRENT_TIME} (${file_type}:${file_name})/${file_name}"
# 将文件的信息写入到info文件中
echo "$dir_path" >"${TRASH_DIR}/${CURRENT_TIME} (${file_type}:${file_name})/info"
echo "${TRASH_DIR}/${CURRENT_TIME} (${file_type}:${file_name})/${file_name}" >>"${TRASH_DIR}/${CURRENT_TIME} (${file_type}:${file_name})/info"
if [[ "$?" -eq 0 ]]; then
echo "已将 $file_name 移动到垃圾桶目录:${TRASH_DIR}/${CURRENT_TIME} (${file_type}:${file_name})"
else
# 如果移动文件时出现错误,则输出错误消息,并尝试恢复删除的文件
echo "移动 $file_name 到垃圾桶目录时出现错误"
last_file="${CURRENT_TIME} (${file_type}:${file_name})"
mv "${TRASH_DIR}/${last_file}/${file_name}" "$dir_path/"
rm -rf "${TRASH_DIR}/${last_file}"
echo "尝试恢复 $file_name"
fi
else
echo "$arg 不是一个有效的文件或目录2"
fi
done
;;
esac
# echo "is_add_alias=$is_add_alias"
# echo "shell=$shell"
# echo "rc_file=$rc_file"
#source需要在最后执行,如果is_add_alias=1,说明脚本重新修改过rc_file添加别名,那么就需要重新运行zsh或者bash
if [[ "$is_add_alias" -eq 1 ]]; then
$shell
fi