[comp.unix.questions] Creating a nondestructive 'rm'

burdick@hpindda.HP.COM (Matt Burdick) (11/04/88)

Does anyone have a nice alias or shell script so that when I 'rm' a file it
will actually move it to, for instance, a tmp subdirectory in my home
directory?

Right now, I have an alias that does this, but the problem is that may
alias doesn't handle all of rm's flags.  For instance, it complains if I
type in 'rm -rf *', since 'mv' doesn't have the -r flag and also can't move
across file partitions.

-- 
Matt Burdick                    | Hewlett-Packard
burdick%hpda@hplabs.hp.com      | Technical Communications Lab

maart@cs.vu.nl (Maarten Litmaath) (11/05/88)

In article <4460006@hpindda.HP.COM> burdick@hpindda.HP.COM (Matt Burdick) writes:
\Does anyone have a nice alias or shell script so that when I 'rm' a file it
\will actually move it to, for instance, a tmp subdirectory in my home
\directory?
\
\Right now, I have an alias that does this, but the problem is that may
\alias doesn't handle all of rm's flags.  For instance, it complains if I
\type in 'rm -rf *', since 'mv' doesn't have the -r flag and also can't move
\across file partitions.

How about the following csh hacks?
You needn't use the `-rf' flags anymore, just type `rm directory'!

alias	rm	'set r=(\!* /); _rm'
alias	_rm	'if ($#r > 1) eval /bin/mv $r[1] ~/tmp \|\|'\
		'/bin/cp -r $r[1] ~/tmp \&\& /bin/rm -r $r[1]\; shift r\; _rm'
-- 
George Bush:                          |Maarten Litmaath @ VU Amsterdam:
             Capt. Slip of the Tongue |maart@cs.vu.nl, mcvax!botter!maart

tondu@felix.UUCP (Walter Tondu) (11/06/88)

In article <4460006@hpindda.HP.COM> burdick@hpindda.HP.COM (Matt Burdick) writes:
>Does anyone have a nice alias or shell script so that when I 'rm' a file it
>will actually move it to, for instance, a tmp subdirectory in my home
>directory?
>
>Right now, I have an alias that does this, but the problem is that may
>alias doesn't handle all of rm's flags.  For instance, it complains if I
>type in 'rm -rf *', since 'mv' doesn't have the -r flag and also can't move
>across file partitions.
>
>-- 
>Matt Burdick                    | Hewlett-Packard
>burdick%hpda@hplabs.hp.com      | Technical Communications Lab

Here's a solution which I use.  This comes from "Tricks of the UNIX
Masters" by Russel G. Sage. Published by Howard W. Sams & Co.
I have modified it a tad in order to fit my needs more exactly
usage:
    1)	rm -l;	- lists all files that have been removed to
		- 'can' directory.
    2)	rm -r;	- remove permanently all files that have been
		- previously removed.
    3)	rm -f;	- use /bin/rm and force removal.

(strip following code of leading "*".

*:
*# @(#) can v1.0 Maintain trash can
*
*CAN=$HOME/.trashcan
*
*#if [ ! d $CAN ]
*#then
*#	mkdir $CAN
*#fi
*
*if [ $# -eq 0 ]
*then
*	echo "usage can [-l] [-r] [-f] file [file ...]" >&2
*	exit 0
*fi
*
*if [ "`echo \"$1\" | cut -c1`" = "-" ]
*then
*	case $1 in
*	-l)	echo "$CAN:"
*		/bin/ls $CAN
*		exit 0;;
*	-r)	echo "removing $CAN/*:"
*		/bin/rm -rf $CAN/*
*		exit 0;;
*	-f)	echo "force removal"
*		/bin/rm -rf $@
*		exit 0;;
*	-?)	echo "usage can [-l] [-r] file [file ...]" >&2
*		exit 0;;
*	esac
*fi
*
*mv $@ $CAN


____________________________________________________________
.plan - to indulge whenever possible
	
    walter tondu
____________________________________________________________

marki@hpiacla.HP.COM (Mark Ikemoto) (11/06/88)

Try this.  It runs a little too slow for me but maybe you're running on
a faster machine.  

( Matt, for the b_on, b_off, u_on, u_off script variables, you'll need to
replace <esc> with the real escape character so the bolding will work
on your HP terminal.  I deliberately used <esc> so other people's non-HP
terminals accessing Notes wouldn't get screwed up by the escape chars
when they looked at this script. )


Mark

------------ CUT LINE: Remove this line and everything above it ------------
 # Bourne shell script
#######################################################################
#
# @(#) TITLE:     Safer rm command
#
#
# DESCRIPTION:    See user man page below.
#
# INSTALLATION:
#
#    Save this to a file.  Change user-defined shell vars after the
#    script header to your taste and terminal.  Set the permissions on
#    this script to 'execute'.  Invoke this script with no parameters
#    to see the embedded man page.
#
# UPDATE HISTORY:
#
# what date/who      type of change
# ---- --------      --------------
#      01/18/88
#      Mark Ikemoto  >Script created.
#
#      03/03/88
#      Mark Ikemoto  >Added touch command to give file the current time
#                     so it isn't cleaned up until it is older than the
#                     system backup timestamp file.
#
# @(#) 11/05/88
#      Mark Ikemoto  >Added much more comments.
#                    >Added pseudo support for the -f option.
#                    >Isolated HP terminal escape chars into shell vars.
#
#######################################################################
# Shell user-defined functions 
#  


####################################################################
# Start of main commands
#


#-------------------------------------------------------------------
# USER MUST SET THESE VARIABLES FOR INSTALLATION OF THIS SCRIPT
# USER MUST SET THESE VARIABLES FOR INSTALLATION OF THIS SCRIPT
# USER MUST SET THESE VARIABLES FOR INSTALLATION OF THIS SCRIPT
#

junkdirname="$HOME/.rmfiles"  # name of user junk file subdir
rmlogfile="rmlogfile"         # list of removed files

# bolding/underlining for error messages and for keywords in the
# man page...

b_on='<esc>&dB'    # set bolding on for HP terminal
b_off='<esc>&d@'   # set bolding off for HP terminal

u_on='<esc>&dD'    # set underlining on for HP terminal
u_off='<esc>&d@'   # set underlining off for HP terminal

                # For other types of terminals, substitute appropo
                # terminal char sequences or just set b_on, b_off,
                # u_on, u_off to empty ('').

#-------------------------------------------------------------------
# Initialize some global vars
#
PATH=/bin:/usr/bin:/usr/contrib/bin:/usr/local/bin:/etc:.
TZ=PST8PDT; export TZ  # set time zone for invocations of cmds like date, etc.
options=
export options
fname=`basename $0`
cap_fname=`echo $fname | tr "[a-z]" "[A-Z]"`  # make filename uppercase
cmdline=$*    # used in help-checking later on

                     # default settings:
overwrite='false'    # force overwriting; don't prompt for user verify? 
interactive='false'  # user wants to verify copy for each source file?
removedir='false'    # user wants to remove a subdir?  default = no.


#-------------------------------------------------------------------
# Set up some traps for signals.  NOTE that any shell variables used
#    in the trap routine must have been defined previous to this trap
#    declaration.
#
trap "echo $cap_fname:  Type  $fname -h\<ret\>  for help.'; exit 2" \
      1 2 3 6 7 15


#-------------------------------------------------------------------
# Grab args from command line
#
set -- `getopt ifrh $*`  # check if a valid command line entered
if [ $? -ne 0 ]
then
   echo "$b_on $cap_fname: Bad runstring option.$b_off"
   echo "$b_on    Type  $fname -h<ret> for help.$b_off " 
   exit 2
fi


#-------------------------------------------------------------------
# Parse command args
#
help_wanted=false 

while [ $# -gt 0 ]
do
   case $1 in
   -i)  interactive='true';;
   -f)  ;;
   -r)  removedir='true';;
   -h)  help_wanted='true';;
   --)  shift; break;; # end of options in command line;
                       # source/target names should remain in runstring
   *)   echo "$b_on $cap_fname: Bad runstring option.$b_off "
        echo "$b_on    Type  $fname -h<ret> for help.$b_off " 
        exit 2;;
   esac
   shift
done


#-------------------------------------------------------------------
# Does user want a help screen?
#
if [ -z "$cmdline" -o "$help_wanted" = 'true' ]
then
   more <<!

   $b_on NAME$b_off 
        $cap_fname  --  Safer rm command

   $b_on SYNOPSIS$b_off 
        $b_on $fname  [-i] [-f] [-r <subdir>]  <file> ... $b_off 
        $b_on $fname  -h$b_off 

   $b_on DESCRIPTION$b_off 
        A safer rm command.  Will "delete" files/subdirs and move
        them to a user "junk file" subdirectory.  The current junk dir
        name being used is  $b_on $junkdirname $b_off .  This junk dir
        name is hardcoded into this script.  This script assumes that
        the junk dir already exists before calling this script.

        The $u_on arguments$u_off  are:
   
           $b_on <-i>$b_off            will cause  $fname  to obtain authorization
                          from the user before removing each source file.
   
           $b_on <-f>$b_off            does absolutely nothing and is only accepted
                          for compatibility with the real rm's runstring
                          options.
   
           $b_on <-r>$b_off            will cause  $fname  to remove the subdir
                          specified in the runstring.  The subdir tree
                          structure is moved to the junk file subdir intact
                          before the source subdir is removed.
   
           $b_on <file>  ...$b_off    are the names of the files/dirs to delete.

           $b_on -h$b_off              will cause this help screen to be displayed.
                          This is the same as typing  $fname  <ret>
                          with no parameters.
  

        Files deleted can be unremoved by the user; the unremoval must
        be done by hand (sorry).  This is because the user might have
        removed several files with the same name and the user would have
        to go into each file anyway to figure out which is the file that
        they want to unremove.

        When a file/dir is moved to the junk directory, it is renamed
        with a unique filename so that future rm'ing of files with the
        same name will not cause files in the junk directory to be
        overwritten.

        However, if all of the removed files in the junk directory have
        unique, non-user-friendly names, then the user will have a heck
        of a time trying to figure out which file is the one they want
        if they want to restore a file.

        So, before files are renamed during the remove process, their
        original name and their new unique name will be placed as an entry
        in a listing file, $b_on $junkdirname/$rmlogfile $b_off , along
        with a prefix, 'f' or 'd', to indicate whether the entry is a
        removed file or directory, respectively.

        Note that once a file/dir has been unremoved, its entry in the
        listing file still exists.  The user must remove their entry
        from this listing by hand (sorry again).


   $b_on WARNING$b_off
        This script is not portable because of the use of HP terminal
        escape sequences in the man page and error messages.  You must
        alter script shell vars to make this portable.

   $b_on AUTHOR$b_off
        HP R&D Lab.

!
   exit 0
fi

#
#----------------------------------------------------------------------
#  Do the main loop

while [ $# -gt 0 ]
do
   if [ "$interactive" = 'true' ]
   then
      echo "Remove  $1? (y/n/a[bort]) \c" 
      read answer

      if [ "$answer" = 'a' ]
      then
         exit 0
      fi

      if [ "$answer" != 'y' -a "$answer" != 'Y' ]
      then
         shift   # go on to next source file name
         continue
      fi
   fi

   if [ -d "$1" ]
   then
      if [ $removedir != 'true' ]
      then
	 echo "$b_on $cap_fname: Specified directory removal of  $1$b_off "
	 echo "$b_on    but remove-dir option (-r) not specified.$b_off "
	 echo "$b_on    Type  $fname -h<ret> for help.$b_off " 
         shift
         continue  # bypass this name; go to the next one
      fi
      # create a subdir inside the junk file subdir in which to
      #   deposit "removed" subdir file(s)
      #
      rmname="`expr substr $LOGNAME 1 4``date +%m%d%H%M%S`"
      mkdir $junkdirname/$rmname
      touch "$junkdirname/$rmname"  # give file the current time to aid in
                             # cleaning up the junk dir using this timestamp
      cp -r $1 $junkdirname/$rmname
      /bin/rm -r $1
      echo "d	$1		$rmname" >> $junkdirname/$rmlogfile
   else
      rmname="`expr substr $LOGNAME 1 4``date +%m%d%H%M%S`"
      /bin/mv $1 "$junkdirname/$rmname"
      touch "$junkdirname/$rmname"  # give file the current time to aid in
                             # cleaning up the junk dir using this timestamp
      if [ $? != 0 ]
      then
	 echo "$b_on $cap_fname: Cannot access file/subdir  $1  .$b_off "
	 echo "$b_on    Consult man pages or$b_off " 
	 echo "$b_on    type  $fname -h<ret> for help.$b_off " 
         exit 1
      fi
      echo "f	$1		$rmname" >> $junkdirname/$rmlogfile
   fi

   shift
done 

exit 0  # removal complete


#######################################################################
# IMPLEMENTATION NOTES/ENHANCEMENT REMINDERS:
#
# 1. I was giving some consideration into making this a C program so that
#    it would run faster.  Currently, it is too slow for my tastes.
#
#    Note that one problem in making this thing run faster would be in
#    timestamping the files uniquely, especially if you are copying/renaming
#    several short files all at the same time.  The system clock would
#    not have enough opportunity to change to a unique set of digits.
#
# 2. Instead of uniquely identifying duplicately-named removed files via
#    timestamping in the filename, how about keeping the names the same
#    as the originals as much as possible but adding number suffixes if
#    removing duplicately-named files.  This eliminates the need for
#    an rmlogfile to map timestamped names to real names.
#
# 3. We give the removed files the current mod timestamp so the files'
#    mod timestamp could be used by some sort of cleanup script to do
#    periodic cleanup of the junk dir.  Maybe the cleanup script could
#    be run during login.
#
# 4. If I had more time, I could develop an 'unrm' command script that,
#    when invoked with the name of the file/dir to unremove, would
#    look in the removed-file listing and display all entries matching
#    the user-specified filename.  We could provide unique sequence
#    numbers for each entry, so that the user, after seeing the display
#    of candidate entries, could specify the index number of the
#    entry to unremove.
#
#    This script would remove the entry from the listing after
#    after completion of the removal of the file/dir itself.
#
#    The other problem that must be overcome is that even if we display
#    the candidate list entries to the user for unremoval, the user still
#    may not be able to tell which file is which if there are several
#    entries with the same original file name.  The fix for this would
#    be to allow the user during rm'ing to specify a comment to be added
#    to the list file entry for that file.  When the user asks to
#    unremove a file and is shown the list entries of possible candidates,
#    the user will also see the comment field for each entry.
#

logan@vsedev.VSE.COM (James Logan III) (11/06/88)

In article <4460006@hpindda.HP.COM> burdick@hpindda.HP.COM (Matt Burdick)
writes:
>Does anyone have a nice alias or shell script so that when I 'rm' a file it
>will actually move it to, for instance, a tmp subdirectory in my home
>directory?

Yes, I do.  I just wrote it last week and have not thouroughly
tested it, but as far as I can tell, it works fine.  My version
COPIES each file, rather than moving it so that it can be used
across partitions.  Unfortunately, you must have read access to
the file in order to remove it with this script.  If you don't
have read access, just use /bin/rm or move the file manually.  

Basically, rm() copies each file you want removed to a directory
called "trash" in your $HOME directory.  If you want this trash
directory to be called something else, just set the environment
variable "TRASH" to the full pathname of the directory.

When invoked with the -r option, rm() will copy every file under
the specified directory and remove the directory.  Each file is
copied to $TRASH, but the original hierarchy is not retained so
be careful when you remove two files with the same name in
different sub-directories!

The -i flag works pretty much like the real rm.

Once a month I usually use the "discard" function to remove all
of these files with /bin/rm for real.  It will prompt you for
permission to delete each file unless you use the -f option.
One problem with discard() is that I forgot to make it remove
invisible files that have filenames beginning with ".".

(Sorry that I haven't written a man page for these yet -- I just
haven't gotten around to it...  If there is any interest I will
post the man pages sometime next week or so...) 

I define these functions in a file called ".kshrc" in my home
directory and set the ksh's ENV variable so that these functions
will be read by each sub-shell.  

I wrote these functions to be portable between the Bourne shell
and the Korn shell.  Just send me email if you want details on
using these functions in Bourne shell-script form...   

I used only variable names that start with an underscore so that
they would not interfere with any other scripts.  Also, I unset
the variables I use in rm() since the option flags stay permanently
set otherwise.

If you find any bugs or can think of any enhancements, just send
me email.

			-Jim

-------------------------------------------------------------------------------

hastrash() {
	# Written by James Logan (vsedev!logan) 11/88
	# Check existence of $TRASH

	if test -z "$TRASH"; then
		TRASH=$HOME/trash;
	fi;
	if test ! -d "$TRASH"; then
		echo >&2 "$0: Making trash directory $TRASH...\c";
		if mkdir "$TRASH"; then
			echo >&2 "done.";
		else
			echo >&2 "\n$0: Cannot make directory $TRASH";
			return 1;
		fi;
	fi;
	if test ! -w "$TRASH"; then
		echo >&2 "$0: Cannot write in $TRASH.";
		return 1;
	fi;
	return 0;
}

rm() {
	# Written by James Logan (vsedev!logan) 11/88

	# Take out this next line when you are sure that /bin/rm is
	# not being called by mistake.
	echo >&2 "(rm() function)";

	unset _ARG _RECURSIVE _NOERROR _INTERACTIVE _COMMAND _FILE _RESP;
	hastrash || return 1;
	if test $# = 0; then
		echo >&2 "usage: $0 [-rfi] file ...";
		return 1;
	fi;

	set -- `getopt "rfi" $*`;
	for _ARG in $*; do
		case "$_ARG" in
			-r)
				_RECURSIVE=1;
				;;
			-f)
				_NOERROR=1;
				;;
			-i)
				_INTERACTIVE=1;
				;;
			--)
				shift;
				break;
				;;
		esac;
		shift;
	done;
	if test "$_RECURSIVE"; then
		_COMMAND="find $* -type f -print";
	else
		_COMMAND="for _I in $*; do echo \$_I; done;";
	fi;

	eval $_COMMAND |
	while read _FILE; do
		if test "$_INTERACTIVE"; then
			echo "$_FILE? ";
			read _RESP </dev/tty;
			case "$_RESP" in
			[Yy]*)
				;;
			*)
				continue;
				;;
			esac;
		fi;
		if test ! -f "$_FILE"; then
			echo >&2 "$0: $_FILE: not a file";
			continue;
		fi;
		if cp $_FILE $TRASH; then
			/bin/rm $_FILE;
		else
			return 1;
		fi;
	done;
	if test "$_RECURSIVE"; then
		/bin/rmdir $*;
	fi;
	return 0;
}

discard() {
	# Written by James Logan (vsedev!logan) 11/88
	# Take out the trash

	hastrash || return 1;
	case $# in 
	0)
		;;
	1)
		if test "$1" = "-f"; then
			_FORCE="1";
		else
			echo >&2 "Usage: $0 [ -f ]";
			return 1;
		fi;
		;;
	*)
		echo >&2 "Usage: $0 [ -f ]";
		return 1;
		;;
	esac;
	if test "$_FORCE"; then
		/bin/rm -f $TRASH/*;
		return $?;
	fi;
	for _FILE in $TRASH/*; do
		echo "$_FILE? [y, n, q]:\c";
		read _RESPONSE;
		case "$_RESPONSE" in
		[qQ]*)
			return 0;
			;;
		[yY]*)
			/bin/rm -f $_FILE;
			;;
		[nN]*)
			continue;
			;;
		esac;
	done;
	return 0;
}
-------------------------------------------------------------------------------
-- 
Jim Logan		logan@vsedev.vse.com
(703) 892-0002		uucp:	..!uunet!vsedev!logan
			inet:	logan%vsedev.vse.com@uunet.uu.net

gwyn@smoke.BRL.MIL (Doug Gwyn ) (11/10/88)

>In article <4460006@hpindda.HP.COM> burdick@hpindda.HP.COM (Matt Burdick) writes:
>>Does anyone have a nice alias or shell script so that when I 'rm' a file it
>>will actually move it to, for instance, a tmp subdirectory in my home
>>directory?

I don't know why you don't want the command that removes links to inodes
to do anything other than remove links to inodes.  However, you might
consider using the Adventure shell I posted a few years ago.  It supports
resurrection of objects that had been destroyed (sent into limbo).

jerryp@cmx.npac.syr.edu (Jerry Peek) (11/28/88)

In article <67991@felix.UUCP> tondu@felix.UUCP (Walter Tondu) writes:
- In article <4460006@hpindda.HP.COM> burdick@hpindda.HP.COM (Matt Burdick) writes:
- Here's a solution which I use.  This comes from "Tricks of the UNIX
- Masters" by Russel G. Sage. Published by Howard W. Sams & Co.
- I have modified it a tad in order to fit my needs more exactly

- if [ "`echo \"$1\" | cut -c1`" = "-" ]
- then
-         case $1 in
-         -l)        echo "$CAN:"
-                 /bin/ls $CAN
-                 exit 0;;
-         -r)        echo "removing $CAN/*:"
-                 /bin/rm -rf $CAN/*
-                 exit 0;;
-         -f)        echo "force removal"
-                 /bin/rm -rf $@
-                 exit 0;;
-         -?)        echo "usage can [-l] [-r] file [file ...]" >&2
-                 exit 0;;
-         esac
- fi

Geez.  I don't have "Tricks of the UNIX Masters," and maybe that wasn't
the original script from the book, but it seems like that section of
the code would be a lot more efficient without the (redundant) echo/cut/test.
And it doesn't handle the -f case too well because the $@ picks up the -f.
Here's a quick hack (not tested, but should work) with a few more fixes, too:

case "$1" in
-l)        echo "$CAN:"
        /bin/ls $CAN
        ;;
-r)        echo "removing $CAN/*:"
        /bin/rm -rf $CAN/*
        ;;
-f)        echo "force removal"
        /bin/rm -r $@         # PASSES rm THE -f FROM $1
        ;;
-?)        echo "usage can [-l] [-f] [-r] file [file ...]" >&2
        exit 1
        ;;
esac
exit 0

Not to be picky here, but I think that a script that you use as much as "rm"
should be as efficient as you can make it...

--Jerry Peek, Northeast Parallel Architectures Center, Syracuse, NY
  jerryp@cmx.npac.syr.edu
  +1 315 443-1722

wu@spot.Colorado.EDU (WU SHI-KUEI) (11/29/88)

In article <844@cmx.npac.syr.edu> jerryp@cmx.npac.syr.edu (Jerry Peek) writes:
>In article <67991@felix.UUCP> tondu@felix.UUCP (Walter Tondu) writes:
>- In article <4460006@hpindda.HP.COM> burdick@hpindda.HP.COM (Matt Burdick) writes:
>- Here's a solution which I use.  This comes from "Tricks of the UNIX
>- Masters" by Russel G. Sage. Published by Howard W. Sams & Co.
>- I have modified it a tad in order to fit my needs more exactly
etc., etc.

Just another example of the fact that the proper title of the book is:

	"Tricks of the UNIX Masters, Junior Grade"
                                     ^^^^^^ ^^^^^

In reality: Carl Brandauer
uunet!nbires!bdaemon!carl