目的:防止直接删除文件
通过自定义脚本,将文件移到回收站,而不是直接删除。
脚本地址:https://github.com/kaelzhang/shell-safe-rm
将脚本保存在 ~/mysh/rf.sh
中
配置别名 alias rf='~/mysh/rf.sh -rf'
即可通过 rf 命令删除文件。
附录脚本:
#!/bin/bash
# You could modify these environment variables to change the default constants
# Default to ~/.Trash on Mac, ~/.local/share/Trash/files on Linux.
DEFAULT_TRASH="$HOME/.Trash"
if [[ "$(uname -s)" == "Linux" ]]; then
DEFAULT_TRASH="$HOME/.local/share/Trash/files"
fi
SAFE_RM_TRASH=${SAFE_RM_TRASH:="$DEFAULT_TRASH"}
# Print debug info or not
SAFE_RM_DEBUG=${SAFE_RM_DEBUG:=}
# -------------------------------------------------------------------------------
# Simple basename: /bin/rm -> rm
COMMAND=${0##*/}
# pwd
__DIRNAME=$(pwd)
GUID=0
TIME=
date_time(){
TIME=$(date +%Y-%m-%d_%H:%M:%S)-$GUID
(( GUID += 1 ))
}
# tools
debug(){
if [[ -n "$SAFE_RM_DEBUG" ]]; then
echo "[D] $@" >&2
fi
}
# parse argv -------------------------------------------------------------------------------
invalid_option(){
# if there's an invalid option, `rm` only takes the second char of the option string
# case:
# rm -c
# -> rm: illegal option -- c
echo "rm: illegal option -- ${1:1:1}"
usage
}
usage(){
echo "usage: rm [-f | -i | -I] [-dPRrvW] file ..."
echo " unlink file"
# if has an invalid option, exit with 64
exit 64
}
if [[ "$#" = 0 ]]; then
echo "safe-rm"
usage
fi
ARG_END=
FILE_NAME=
ARG=
file_i=0
arg_i=0
split_push_arg(){
# remove leading '-' and split combined short options
# -vif -> vif -> v, i, f
split=`echo ${1:1} | fold -w1`
local arg
for arg in ${split[@]}; do
ARG[arg_i]="-$arg"
((arg_i += 1))
done
}
push_arg(){
ARG[arg_i]=$1
((arg_i += 1))
}
push_file(){
FILE_NAME[file_i]=$1
((file_i += 1))
}
# pre-parse argument vector
while [[ -n $1 ]]; do
# case:
# rm -v abc -r --force
# -> -r will be ignored
# -> args: ['-v'], files: ['abc', '-r', 'force']
if [[ -n $ARG_END ]]; then
push_file "$1"
else
case $1 in
# case:
# rm -v -f -i a b
# case:
# rm -vf -ir a b
# ATTENTION:
# Regex in bash is not perl regex,
# in which `'*'` means "anything" (including nothing)
-[a-zA-Z]*)
split_push_arg $1; debug "short option $1"
;;
# rm --force a
--[a-zA-Z]*)
push_arg $1; debug "option $1"
;;
# rm -- -a
--)
ARG_END=1; debug "divider"
;;
# case:
# rm -
# -> args: [], files: ['-']
*)
push_file "$1"; debug "file $1"
ARG_END=1
;;
esac
fi
shift
done
# flags
OPT_FORCE=
OPT_INTERACTIVE=
OPT_INTERACTIVE_ONCE=
OPT_RECURSIVE=
OPT_VERBOSE=
# global exit code, default to 0
EXIT_CODE=0
# parse options
for arg in ${ARG[@]}; do
case $arg in
# There's no --help|-h option for rm on Mac OS
# [hH]|--[hH]elp)
# help
# shift
# ;;
-f|--force)
OPT_FORCE=1; debug "force : $arg"
;;
# interactive=always
-i|--interactive|--interactive=always)
OPT_INTERACTIVE=1; debug "interactive : $arg"
OPT_INTERACTIVE_ONCE=
;;
# interactive=once. interactive=once and interactive=always are exclusive
-I|--interactive=once)
OPT_INTERACTIVE_ONCE=1; debug "interactive_once : $arg"
OPT_INTERACTIVE=;
;;
# both r and R is allowed
-[rR]|--[rR]ecursive)
OPT_RECURSIVE=1; debug "recursive : $arg"
;;
# only lowercase v is allowed
-v|--verbose)
OPT_VERBOSE=1; debug "verbose : $arg"
;;
*)
invalid_option $arg
;;
esac
done
# /parse argv -------------------------------------------------------------------------------
# make sure recycled bin exists
if [[ ! -e $SAFE_RM_TRASH ]]; then
echo "Directory \"$SAFE_RM_TRASH\" does not exist, do you want create it?"
echo -n "(yes/no): "
read answer
if [[ $answer = "yes" || ! -n $anwser ]]; then
mkdir -p "$SAFE_RM_TRASH"
else
echo "Canceled!"
exit 1
fi
fi
# try to remove a file or directory
remove(){
local file=$1
# if is dir
if [[ -d $file ]]; then
# if a directory, and without '-r' option
if [[ ! -n $OPT_RECURSIVE ]]; then
debug "$LINENO: $file: is a directory"
echo "$COMMAND: $file: is a directory"
return 1
fi
if [[ $file = './' ]]; then
echo "$COMMAND: $file: Invalid argument"
return 1
fi
if [[ $OPT_INTERACTIVE = 1 ]]; then
echo -n "examine files in directory $file? "
read answer
# actually, as long as the answer start with 'y', the file will be removed
# default to no remove
if [[ ${answer:0:1} =~ [yY] ]]; then
# if choose to examine the dir, recursively check files first
recursive_remove "$file"
# interact with the dir at last
echo -n "remove $file? "
read answer
if [[ ${answer:0:1} =~ [yY] ]]; then
[[ $(ls -A "$file") ]] && {
echo "$COMMAND: $file: Directory not empty"
return 1
} || {
trash "$file"
debug "$LINENO: trash returned status $?"
}
fi
fi
else
trash "$file"
debug "$LINENO: trash returned status $?"
fi
# if is a file
else
if [[ "$OPT_INTERACTIVE" = 1 ]]; then
echo -n "remove $file? "
read answer
if [[ ${answer:0:1} =~ [yY] ]]; then
:
else
return 0
fi
fi
trash "$file"
debug "$LINENO: trash returned status $?"
fi
}
recursive_remove(){
local path
# use `ls -A` instead of `for` to list hidden files.
# and `for $1/*` is also weird if `$1` is neithor a dir nor existing that will print "$1/*" directly and rudely.
# never use `find $1`, for the searching order is not what we want
local list=$(ls -A "$1")
[[ -n $list ]] && for path in "$list"; do
remove "$1/$path"
done
}
# trash a file or dir directly
trash(){
debug "trash $1"
# origin file path
local file=$1
# the first parameter to be passed to `mv`
local move=$file
local base=$(basename "$file")
local travel=
# basename ./ -> .
# basename ../ -> ..
# basename ../abc -> abc
# basename ../.abc -> .abc
if [[ -d "$file" && ${base:0:1} = '.' ]]; then
# then file must be a relative dir
cd $file
# pwd can't be piped?
move=$(pwd)
move=$(basename "$move")
cd ..
travel=1
fi
local trash_name=$SAFE_RM_TRASH/$base
# if already in the trash
if [[ -e "$trash_name" ]]; then
# renew $TIME
date_time
trash_name="$trash_name-$TIME"
fi
[[ "$OPT_VERBOSE" = 1 ]] && list_files "$file"
debug "mv $move to $trash_name"
mv "$move" "$trash_name"
[[ "$travel" = 1 ]] && cd $__DIRNAME &> /dev/null
#default status
return 0
}
# list all files and maintain outward sequence
# we can't just use `find $file`, 'coz `find` act a inward searching, unlike rm -v
list_files(){
if [[ -d "$1" ]]; then
local list=$(ls -A "$1")
local f
[[ -n $list ]] && for f in "$list"; do
list_files "$1/$f"
done
fi
echo $1
}
# debug: get $FILE_NAME array length
debug "${#FILE_NAME[@]} files or directory to process: ${FILE_NAME[@]}"
# test remove interactive_once: ask for 3 or more files or with recorsive option
if [[ (${#FILE_NAME[@]} > 2 || $OPT_RECURSIVE = 1) && $OPT_INTERACTIVE_ONCE = 1 ]]; then
echo -n "$COMMAND: remove all arguments? "
read answer
# actually, as long as the answer start with 'y', the file will be removed
# default to no remove
if [[ ! ${answer:0:1} =~ [yY] ]]; then
debug "EXIT_CODE $EXIT_CODE"
exit $EXIT_CODE
fi
fi
for file in "${FILE_NAME[@]}"; do
debug "result file $file"
if [[ $file = "/" ]]; then
echo "it is dangerous to operate recursively on /"
echo "are you insane?"
EXIT_CODE=1
# Exit immediately
debug "EXIT_CODE $EXIT_CODE"
exit $EXIT_CODE
fi
if [[ $file = "." || $file = ".." ]]; then
echo "$COMMAND: \".\" and \"..\" may not be removed"
EXIT_CODE=1
continue
fi
#the same check also apply on /. /..
if [[ $(basename $file) = "." || $(basename $file) = ".." ]]; then
echo "$COMMAND: \".\" and \"..\" may not be removed"
EXIT_CODE=1
continue
fi
# deal with wildcard and also, redirect error output
ls_result=$(ls -d "$file" 2> /dev/null)
# debug
debug "ls_result: $ls_result"
if [[ -n "$ls_result" ]]; then
for file in "$ls_result"; do
remove "$file"
status=$?
debug "remove returned status: $status"
if [[ ! $status == 0 ]]; then
EXIT_CODE=1
fi
done
else
echo "$COMMAND: $file: No such file or directory"
EXIT_CODE=1
fi
done
debug "EXIT_CODE $EXIT_CODE"
exit $EXIT_CODE