I know this is an old post but I am sure it is still read by many people (actually it's one of the first results when searching for "linux undo rm"). Therefore I expect to prevent this situations.
- For the mv issue: my best solution is to alias mv to make backups of the overwritten target filenames as a default:
Code:
alias mv='mv -b'
# or
alias mv='mv --backup=numbered'
Or alike. (backup can be numbered, simple, existing or none)
- For the rm issue, the simplest solution is to alias rm to mv as
Code:
alias rm='mv --backup=numbered --target-directory=$HOME/.trash'
But as some solutions proposed above, whenever rm is called with extra options this will fail. This encouraged me to write a bash script that mimics rm options but actually moves files to a $HOME/.trash folder with backups. It makes use of the getopt utility and therefore inherits its whitespace problems, so BEWARE OF WHITESPACE. Here it is.
Code:
#!/bin/bash
TRASH="$HOME/.trash"
mkdir -p $TRASH || exit 1
# Parse options or die
ARGS="$(getopt \
--options "fiIrRvh" \
--longoptions "force,interactive:,one-file-system,no-preserve-root,preserve-root,recursive,verbose,help,version" \
-- "$@")" || exit 1
# A little magic
# http://linuxwell.com/2011/07/14/getopt-in-bash/
# http://www.linuxjournal.com/content/bash-preserving-whitespace-using-set-and-eval
eval set -- "$ARGS"
# Manage options
FORCE="false"
INTERACTIVE="never"
RECURSIVE="false"
VERBOSE=""
while true; do case "$1" in
-f|--force)
FORCE="true"
INTERACTIVE="never"
shift;;
-i)
INTERACTIVE="always"
shift;;
-I)
INTERACTIVE="once"
shift;;
--interactive)
case "$2" in
never)
INTERACTIVE="never";;
once)
INTERACTIVE="once";;
always)
INTERACTIVE="always";;
*) exit 1;;
esac
shift 2;;
# Silently ignored options
--one-file-system) shift;;
--no-preserve-root) shift;;
--preserve-root) shift;;
-r|-R|--recursive)
RECURSIVE="true"
shift;;
-v|--verbose)
VERBOSE="--verbose"
shift;;
-h|--help|--version)
echo "This is not 'rm'. This is a custom script that tries its best to mimic 'rm' while moving stuff to $TRASH"
exit 0;;
--)
shift
break;;
*) # Shall *not* happen
exit 1;;
esac; done
# Some definitions
RM="mv $VERBOSE --backup=numbered --target-directory=$TRASH"
function prompt {
read -p "Are you sure you want to remove $* ? [y(es)/n(o)] " YN
case $YN in
y|Y|yes|Yes|YES)
echo "true" ;;
*)
echo "false" ;;
esac
}
# Flow control and eventual mv
for FILE in "$@"; do
if test -L "$FILE"
then
/bin/rm "$FILE"
else
if test ! -e "$FILE"
then
if $FORCE
then
true # Skip to next file
else
echo "$FILE does not exist"
exit 1
fi
else
if test ! -d "$FILE" || $RECURSIVE
then
case $INTERACTIVE in
never)
$RM "$FILE";;
once)
if test $# -gt 3 || test -d "$1" || test -d "$2" || test -d "$3"
then
if $(prompt "$*")
then
$RM "$FILE"
INTERACTIVE="never"
else
exit 0
fi
else
$RM "$FILE"
INTERACTIVE="never"
fi;;
always)
if $(prompt "$FILE"); then
$RM "$FILE"
fi;;
*)
echo "WTF"
exit 1;;
esac
else
echo "$FILE is a directory"
exit 1
fi
fi
fi
done
Some newer implementations of rm like "rm (GNU coreutils) 8.21" also accept the "-d|--dir" option to remove empty folders. I haven't implemented it.
If you are working with large files in different partitions or network filesystems, this script can involve unnecessary heavy load. I use a custom remove function instead of the $RM $FILE line:
Code:
function remove {
MOUNT_POINT="$(df "$1" | tail -1 | grep -o '\S*$' )"
case $MOUNT_POINT in
/home)
mv $VERBOSE --backup=numbered --target-directory=$TRASH "$1";;
/mnt/shared)
mv $VERBOSE --backup=numbered --target-directory=$NFS_TRASH "$1";;
*)
/bin/rm -rf "$1"
esac
}
Mind to flush the trash folder weekly
I hope this helps the community!
Edit: It turns out that quoting "$FILE" solves most whitespace issues.