This script implements a command line level trashcan system.
Designed to work around rm
doing the bits needed to implement a trashcan system at command line level. Any user flags hands the request over to rm
. This means rm -f
will still delete a file with no recovery. Prevents execution by root as too many dependencies at superuser level. Deployment by adding aliases rm=trash
and rmoops=untrash
. Only implemented on files, does not cater for directories.
Appreciate any and all suggestions from formatting, expansion of the idea to glaring issues. Looking for an experienced eye to pin point potential issues.
/usr/local/bin/trash
#!/bin/bash
#
# Trashcan script
# alias rm=trash
#
error() {
echo 1ドル
exit 1
}
bypass() {
exec rm $*
}
file="1ドル"
trash=~/.trash
keep=5
## bypass scenarios
# root user
[ "$(id -u)" = "0" ] && error "Please do not run as root user."
# special cases
case $file in
-*) bypass $* ;;
$trash/* ) bypass $* ;;
esac
# only single filename supported
[ $# -gt 1 ] && bypass $*
# can we access the file?
[ -f "$file" ] || error "File not specified."
# move existing versions out of the way
ls $trash/$file.version.* 2>/dev/null | tac | while read name; do
version=$((${name##*.}+1))
# keep the only the last number of versions
if [ $version -gt $keep ]; then
rm $name || error "Unable to remove file $name."
else
mv $name $trash/$file.version.$version || error "Unable to move version $version."
fi
done
# move file into trash (volume friendly)
cp --preserve $file $trash/$file.version.1 || error "Unable to trash your file."
rm -f $file || error "Unable to cleanup the file."
exit 0
/usr/local/bin/untrash
#!/bin/bash
#
# Restore files from Trashcan
# alias rmoops=untrash
#
error() {
echo 1ドル
exit 1
}
file="1ドル"
trash=~/.trash
keep=5
## bypass scenarios
# root user
[ "$(id -u)" = "0" ] && error "Please do not run as root user."
# any flags so rm -rf actually does it
case $file in
-*) error "Flags are not supported." ;;
$trash/*) error "You cant restore from trash directly." ;;
esac
# only single filename supported
[ $# -ne 1 ] && error "File not specified."
version=0
# find latest version
for name in `ls $trash/$file.version.* 2>/dev/null | tac`
do
version=${name##*.}
break
done
[ $version -gt 0 ] || error "Sorry I cannot find your file in the trash."
echo "I have versions 1 to $version, 1 being the most recent."
echo -n "Which would you like to restore? [1]:"
read user
[ -n "$user" ] || user=1
# check we have the actual file
[ -f $trash/$file.version.$user ] || error "Sorry I cannot find that version."
# move existing file out of the way
[ -f $file ] && mv $file $file.old
# restore the file
cp $trash/$file.version.$user $file || error "Unable to restore file."
echo Version $user restored.
exit 0
-
\$\begingroup\$ Welcome to Code Review! Hoe you get some good answers. \$\endgroup\$Phrancis– Phrancis2017年03月09日 08:07:16 +00:00Commented Mar 9, 2017 at 8:07
1 Answer 1
Overall
I'm not really convinced of the value of this "trashcan" idea. If deleting files doesn't actually delete them, then what's the point? Trying to create free space would be ineffectual. Worse, encouraging users to lean on an "undelete" facility will likely leave them in trouble when they move to a conventional system without such a crutch. (Plus, it's a Good Thing to have an occasional minor accident - otherwise it's very easy to just assume that your backups are working, and never think to verify that you can actually restore from them! I'm certainly guilty of that...)
Quoting issues
Much of your code doesn't quote expanded variables when it should. One example:
case $file in
-*) bypass $* ;;
$trash/* ) bypass $* ;;
esac
This is unsafe, and needs to be written
case "$file" in
-*) bypass "$@" ;;
"$trash"/* ) bypass "$@" ;;
esac
There are many more similar instances; I won't list them all.
Testing for file-ness rather than existence
[ -f "$file" ] || error "File not specified."
Do you really want this error when you ask to trash a symlink? In fact, I think the wording of the message is poor, even for the case you think you're testing.
Pathname matching
You test whether the argument is in the "trash" directory with
case $file in
$trash/* )
esac
There's other ways to refer to the same directory as "$trash"
- in particular, a relative name from the working directory. You'll need to canonicalise path name if you're doing this kind of comparison; possibly you might want to compare device and inode numbers for real robustness if you might have bind mounts and the like.
Echo and read
This code:
echo -n "Which would you like to restore? [1]:"
read user
is usually written
read -p "Which would you like to restore? [1]: " user
(I'm not sure that user
is a good name for the version number, either).
-
\$\begingroup\$ The issue it attempts to address is the file delete with no recovery in unix systems. I take your point about freeing space. It is an interesting conundrum. How to have a user level trashcan and yet give the freedom to clean up files. I also realised that I am not inlining this very well with rm. I actually want the script to be a filter to the rm command that basically takes a copy before rm delete's it so I will address this. I think the simplest way is on first use a user is notified of the trashcan feature, how to use it and how to bypass it if needed. \$\endgroup\$Kevin Davies– Kevin Davies2017年03月10日 00:45:51 +00:00Commented Mar 10, 2017 at 0:45
-
\$\begingroup\$ The bit I don't understand is the bit about "no recovery". Are your backups failing? If so, I suggest fixing them rather than trying to work around the problem. Perhaps it's just me that doesn't "get it", though? \$\endgroup\$Toby Speight– Toby Speight2017年03月10日 08:07:07 +00:00Commented Mar 10, 2017 at 8:07