16 Working with the Removed File Archive


#16 Working with the Removed File Archive

Now that a directory of deleted files and directories is hidden within the user's account home, a script to let the user pick and choose between these deleted files would clearly be useful. However, it's quite a task to address all the possible situations, ranging from no matches to one match to more than one match. In the case of more than one match, for example, do you automatically pick the newest file to undelete? Indicate how many matches there are and quit? Present data on the different versions and let the user pick? Let's see what we can do....

The Code

 #!/bin/sh # unrm - Searches the deleted files archive for the specified file or directory. #   If there is more than one matching result, shows a list of the results, #   ordered by timestamp, and lets the user specify which one to restore. mydir="$HOME/.deleted-files" realrm="/bin/rm" move="/bin/mv" dest=$(pwd) if [ ! -d $mydir ] ; then   echo " 
 #!/bin/sh # unrm - Searches the deleted files archive for the specified file or directory. # If there is more than one matching result, shows a list of the results, # ordered by timestamp, and lets the user specify which one to restore. mydir="$HOME/.deleted-files" realrm="/bin/rm" move="/bin/mv" dest=$(pwd) if [ ! -d $mydir ] ; then echo "$0: No deleted files directory: nothing to unrm" >&2 ; exit 1 fi cd $mydir if [ $# -eq 0 ] ; then # no args, just show listing echo "Contents of your deleted files archive (sorted by date):" ls -FC  sed -e 's/\([[:digit:]][[:digit:]]\.\)\{5\}//g' \ -e 's/^/ /' exit 0 fi # Otherwise we must have a user-specified pattern to work with. Let's see if the # pattern matches more than one file or directory in the archive. 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 for name in $(ls -td *"$1") do datetime="$(echo $name  cut -c1-14 \ awk -F. '{ print $5"/"$4" at "$3":"$2":"$1 }')" if [ -d $name ] ; then size="$(ls $name  wc -l  sed 's/[^[:digit:]]//g')" echo " $index) $1 (contents = ${size} items, 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 "$0: Restore canceled by user: index value too big." >&2 exit 1 fi if [ $desired -lt 1 ] ; then echo "$0: restore 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 
: No deleted files directory: nothing to unrm" >&2 ; exit 1 fi cd $mydir if [ $# -eq 0 ] ; then # no args, just show listing echo "Contents of your deleted files archive (sorted by date):" ls -FC sed -e 's/\([[:digit:]][[:digit:]]\.\)\{5\}//g' \ -e 's/^/ /' exit 0 fi # Otherwise we must have a user-specified pattern to work with. Let's see if the # pattern matches more than one file or directory in the archive. matches="$(ls *"" 2> /dev/null wc -l)" if [ $matches -eq 0 ] ; then echo "No match for \"\" 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 for name in $(ls -td *"") do datetime="$(echo $name cut -c1-14 \ awk -F. '{ print "/"" at "":"":" }')" if [ -d $name ] ; then size="$(ls $name wc -l sed 's/[^[:digit:]]//g')" echo " $index) (contents = ${size} items, deleted = $datetime)" else size="$(ls -sdk1 $name awk '{print }')" echo " $index) (size = ${size}Kb, deleted = $datetime)" fi index=$(($index + 1)) done echo "" echo -n "Which version of do you want to restore ('0' to quit)? [1] : " read desired if [ ${desired:=1} -ge $index ] ; then echo "
 #!/bin/sh # unrm - Searches the deleted files archive for the specified file or directory. # If there is more than one matching result, shows a list of the results, # ordered by timestamp, and lets the user specify which one to restore. mydir="$HOME/.deleted-files" realrm="/bin/rm" move="/bin/mv" dest=$(pwd) if [ ! -d $mydir ] ; then echo "$0: No deleted files directory: nothing to unrm" >&2 ; exit 1 fi cd $mydir if [ $# -eq 0 ] ; then # no args, just show listing echo "Contents of your deleted files archive (sorted by date):" ls -FC  sed -e 's/\([[:digit:]][[:digit:]]\.\)\{5\}//g' \ -e 's/^/ /' exit 0 fi # Otherwise we must have a user-specified pattern to work with. Let's see if the # pattern matches more than one file or directory in the archive. 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 for name in $(ls -td *"$1") do datetime="$(echo $name  cut -c1-14 \ awk -F. '{ print $5"/"$4" at "$3":"$2":"$1 }')" if [ -d $name ] ; then size="$(ls $name  wc -l  sed 's/[^[:digit:]]//g')" echo " $index) $1 (contents = ${size} items, 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 "$0: Restore canceled by user: index value too big." >&2 exit 1 fi if [ $desired -lt 1 ] ; then echo "$0: restore 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 
: Restore canceled by user: index value too big." >&2 exit 1 fi if [ $desired -lt 1 ] ; then echo "
 #!/bin/sh # unrm - Searches the deleted files archive for the specified file or directory. # If there is more than one matching result, shows a list of the results, # ordered by timestamp, and lets the user specify which one to restore. mydir="$HOME/.deleted-files" realrm="/bin/rm" move="/bin/mv" dest=$(pwd) if [ ! -d $mydir ] ; then echo "$0: No deleted files directory: nothing to unrm" >&2 ; exit 1 fi cd $mydir if [ $# -eq 0 ] ; then # no args, just show listing echo "Contents of your deleted files archive (sorted by date):" ls -FC  sed -e 's/\([[:digit:]][[:digit:]]\.\)\{5\}//g' \ -e 's/^/ /' exit 0 fi # Otherwise we must have a user-specified pattern to work with. Let's see if the # pattern matches more than one file or directory in the archive. 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 for name in $(ls -td *"$1") do datetime="$(echo $name  cut -c1-14 \ awk -F. '{ print $5"/"$4" at "$3":"$2":"$1 }')" if [ -d $name ] ; then size="$(ls $name  wc -l  sed 's/[^[:digit:]]//g')" echo " $index) $1 (contents = ${size} items, 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 "$0: Restore canceled by user: index value too big." >&2 exit 1 fi if [ $desired -lt 1 ] ; then echo "$0: restore 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 
: restore canceled by user." >&2 ; exit 1 fi restore="$(ls -td1 *"" sed -n "${desired}p")" if [ -e "$dest/" ] ; then echo "\"\" already exists in this directory. Cannot overwrite." >&2 exit 1 fi echo -n "Restoring file \"\" ..." $move "$restore" "$dest/" echo "done." echo -n "Delete the additional copies of this file? [y] " read answer if [ ${answer:=y} = "y" ] ; then $realrm -rf *"" echo "deleted." else echo "additional copies retained." fi else if [ -e "$dest/" ] ; then echo "\"\" already exists in this directory. Cannot overwrite." >&2 exit 1 fi restore="$(ls -d *"")" echo -n "Restoring file \"\" ... " $move "$restore" "$dest/" echo "done." fi exit 0

How It Works

The first chunk of code, the if [$# -eq 0] conditional block, executes if no arguments are specified, displaying the contents of the deleted files archive. However, there's a catch. We can't display the actual filenames because we don't want the user to see the timestamp data used internally to guarantee unique filenames. In order to display this data in a more attractive format, the sed statement deletes the first five occurrences of digit digit dot in the ls output.

If an argument is specified, it is the name of a file or directory to recover. The next step is to ascertain how many matches there are for the name specified. This is done with the following statement:

 matches="$(ls *"" 2> /dev/null  wc -l)" 

The unusual use of quotes in the argument to ls ensures that this pattern will match filenames that have embedded spaces, while the '*' wildcard pattern is expanded properly by the shell. The 2> /dev/null ensures that any error resulting from the command is discarded rather than shown to the user. The error that's being discarded is most likely No such file or directory, caused when no match for the specified filename is found.

If there are multiple matches for the file or directory name specified, the most complex part of this script, the if [ $matches -gt 1 ] block, is executed, displaying all the results. Using the -t flag to the ls command in the main for loop causes the archive files to be presented from newest to oldest, and a succinct call to the awk command translates the date/time stamp portion of the filename into the deleted date and time information in the parentheses. The inclusion of the -k flag to ls in the size calculation forces the file sizes to be represented in kilobytes:

 size="$(ls -sdk1 $name  awk '{print }')" 

Rather than displaying the size of matching directory entries, which would be meaningless, the script displays the number of files within each matching directory. The number of entries within a directory is actually quite easy to calculate, and we chop the leading spaces out of the wc command output, as follows :

 size="$(ls $name  wc -l  sed 's/[^[:digit:]]//g')" 

Once the user specifies one of the possible matching files or directories, the corresponding exact filename is identified by the following statement:

 restore="$(ls -td1 *""  sed -n "${desired}p")" 

This statement contains a slightly different use of sed . Specifying the -n flag and then a number ( ${desired} ) followed by the p print command is a very fast way to extract only the specified line number from the input stream.

The rest of the script should be fairly self-explanatory. There's a test to ensure that unrm isn't going to step on an existing copy of the file or directory, and then the file or directory is restored with a call to /bin/mv . Once that's finished, the user is given the chance to remove the additional (probably superfluous) copies of the file, and the script is done.

Running the Script

There are two ways to work with this script. First, without any arguments, it'll show a listing of all files and directories in the deleted files archive for the specific user. Second, with a desired file or directory name as the argument, the script will either restore that file or directory (if there's only one match) or show a list of candidates for restoration, allowing the user to specify which version of the deleted file or directory to restore.

The Results

Without any arguments specified, the script shows what's in the deleted files archive:

 $  unrm  Contents of your deleted files archive (sorted by date):   deitrus                  this is a test   deitrus                  garbage 

When a filename is specified, the script displays more information about the file, as follows:

 $  unrm deitrus  More than one file or directory match in the archive:  1)   deitrus  (size = 7688Kb, deleted = 11/29 at 10:00:12)  2)   deitrus  (size = 4Kb, deleted = 11/29 at 09:59:51) Which version of deitrus do you want to restore ('0' to quit)? [1] :   unrm: restore canceled by user. 

Hacking the Script

If you implement this script, there's a lurking danger that's worth raising. Without any controls or limits, the files and directories in the deleted files archive will grow without bounds. To avoid this, invoke find from within a cron job to prune the deleted files archive. A 14-day archive is probably quite sufficient for most users and will keep things reasonably in check.




Wicked Cool Shell Scripts. 101 Scripts for Linux, Mac OS X, and Unix Systems
Wicked Cool Shell Scripts
ISBN: 1593270127
EAN: 2147483647
Year: 2004
Pages: 150
Authors: Dave Taylor

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net