既然有了一个被删除文件的目录,并且这个目录是在用户的家目录下的隐藏目录,那么一个让用户检索这些已删除文件的脚本的程序就会很是有用了。无论怎样,想要代表全部可能发生的状况是很难的,由于它包括了没有匹配、仅有一个匹配以及多个匹配三种状况。在多个匹配的情形中,好比,你是想要挑出最新的文件,而后将它还原?仍是指明有多少种状况匹配成功,而后就退出?又或是展现下不一样状况的数据而后供用户挑选?下面,让咱们来看看咱们到底都能作些什么... git
#!/bin/sh # unrm.sh -- 查找已删除文档中的给定文件或是目录 # 若是有多个匹配,那么给出一个按时间戳排序的结果列表, # 而后,让用户指定还原哪一个 mydir="$HOME/.deleted-files" realrm="/bin/rm" move="/bin/mv" dest=$(pwd) if [ ! -d $mydir ]; then echo "`basename $0`: No deleted files directory: nothing to unrm" >&2 exit 1 fi cd $mydir if [ $# -eq 0 ]; then echo "Contents of your deleted files archive(sorted by data):" # ls中的-F是给列出来的项增长指示器,好比,文件不加后缀,目录加斜杠/,可执行文件加星号* # ls中的-C是按照列显示 ls -FC | sed -e 's/\([[:digit:]][[:digit:]]\.\)\{5\}//g' \ -e 's/^/ /' # 这条替换的目的是给行头加空格 exit 0 fi # 不然,咱们必须使用一个用户指定的模式。 # 让咱们来看看该模式在文档中是否匹配多个文件或是目录 matches="$(ls *"$1" 2> /dev/null | wc -l)" if [ $matches -eq 0 ]; then echo "No match for \"$1\" in the deleted file archive." >&2 exit 1 fi if [ $matches -gt 1 ]; then echo "More than one file or directory match in the archive:" index=1 # ls中的-t是按照最近修改时间显示 # ls中的-d是只显示目录的名字,而不是显示目录中的内容 for name in $(ls -td *"$1") do datetime="$(echo $name | cut -c1-14 | \ awk -F. '{print $5"/"$4" at "$3":"$2":"$1}')" # 使用awk格式化输出 if [ -d $name ]; then size="$(ls $name | wc -l | sed 's/[^[:digit:]]//g')" # 算出$name这个目录中文件的数目 echo "$index) $1 (contents = ${size} itmes, deleted = $datetime)" else size="$(ls -sdk1 $name | awk '{print $1}')" # 计算文件的大小 echo "$index) $1 (size = ${size}Kb, deleted = $datetime)" fi index=$(($index+1)) done echo "" echo -n "Which version of $1 do you want to restore ('0' to quit)? [1]: " read desired if [ ${desired:=1} -ge $index ]; then echo "$(basename $0): Restore canceled by user: index value too big." >&2 exit 1 fi if [ $desired -lt 1 ]; then echo "$(basename $0): canceled by user." >& 2 exit 1 fi restore="$(ls -td1 *"$1" | sed -n "${desired}p")" if [ -e "$dest/$1" ]; then echo "\"$1\" already exists in this directory. Cannot overwrite." >&2 exit 1 fi echo -n "Restoring file \"$1\"..." $move "$restore" "$dest/$1" echo "done." echo -n "Delete the additional copies of this file? [y]: " read answer if [ ${answer:=y} = "y" ]; then $realrm -rf *"$1" echo "deleted" else echo "additional copies retained." fi else if [ -e "$dest/$1" ]; then echo "\"$1\" already exists in this directory. Cannot Overwrite." >&2 exit 1 fi restore="$(ls -d *"$1")" echo -n "Restoring file \"$1\"..." $move "$restore" "$dest/$1" echo "done." fi exit 0
脚本如何工做:
第一个大段的代码,if [ $# -eq 0 ]条件语块,会按此执行:若是没有参数给定,那么就列出删除文档中的全部内容。但这里有个地方隐瞒了。咱们并不能展现真实的文件名,由于咱们并不想用户看到时间戳,这些时间戳只是用来在内部保护文件名之间并不会相互冲突用的。为了用一种更美观方式展现文件名,sed表达式删除了开始的5个数字段。若是给定了一个参数,它就是要恢复的文件名或是目录了。下一步就是要查明有多少个能匹配给定的名称。下面的语句完成了这个功能:
shell
matches="$(ls *"$1" 2> /dev/null | wc -l)"在ls的参数中有对不经常使用的引号,它们是用来保证该模式会匹配到已嵌入空白的文件名,而通配符'*'则被shell适当的扩展了。而2> /dev/null保住了命令中产生的错误信息会被抛弃掉,不会让它们显示给用户看到。丢弃的信息绝大部分有多是No such file or directory,通常都是因为没找到给定的文件名引发的。若是对给定的文件或是目录名有多个匹配,最复杂的脚本部分,就是if [ $matches -gt 1 ]语块。这个语块,展现了全部的结果。在for循环中的ls命令中使用-t选项,会将文档按照重新到旧排序显示。而后的awk语句将文件名中的时间戳给分割了开来。下面的ls中内含的-k选项是用来计算文件大小时使用千字节(kb)做为单位,而不是平时的字节。
size="$(ls -sdk1 $name | awk '{print $1}')"脚本会在每一个匹配的目录中显示文件的数目,而不是毫无心义的只是显示匹配文件项的大小。一个目录中项的数目事实上很容易计算,使用wc命令:
size="$(ls $name | wc -l | sed 's/[^[:digit:]]//g')"一旦用户给定一种可能的匹配文件或是目录,对应的扩展名就会被下面的语句定义好:
restore="$(ls -td1 *"$1" | sed -n "${desired}p")"这个句子包含了一点sed的另类用法。使用-n选项,而后是一个跟着打印命令p的数字(${desired}),这种方法能够很快的从输入流中提取给定行号的行。
unrm.sh Contents of your deleted files archive(sorted by data): .a.txt .a.o* .adir/有一个文件名作参数:
unrm.sh a.txt More than one file or directory match in the archive: 1) a.txt (size = 4Kb, deleted = 11/18 at 17:15:45) 2) a.txt (size = 4Kb, deleted = 11/18 at 17:15:10) Which version of a.txt do you want to restore ('0' to quit)? [1] 2 Restoring file "a.txt"...done. Delete the additional copies of this file? [y] y deleted分析脚本: 若是你执行这个脚本,那么就有一个潜在的危险须要注意。没有任何控制或是限制的话,在删除文档中的文件或是目录会无限制增长。为了不这点,能够添加一个cronjob来减小文档。保留14天内的文档应当时比较合理的了。