Page 1 of 1

Recycle Bin / Trash Can Functionality for the Linux Shell

Posted: Sat Oct 06, 2007 3:15 pm
by Ollie Saunders
Since I've discovered that it is possible to have this kind of functionality for Samba I've wanted to create a shell utility for moving files to a trash can rather than deleting them forever. I found a simple implementation but I wanted something a little more complete. So I've taken this as an opportunity to actually write something of generalised use in bash.

So I'm posting this for critique and advise, and possibly to see if anyone wants to help me write this, it's a pretty small project and an opportunity to learn a bit about shell scripting. I've read this article about writing good Linux utilities and this about the GNU/POSIX utility guidelines. So I'm enclosing my design here in the hope that more experienced Linuxers :P may be able to offer some conceptual advice.

Note: I haven't written any actual code yet

can

the overall name for the utility is "can" which you can take to loosely mean "put in a trash can". Note 'a' and not 'the'. This is a easy to type, remember and say and is related to what it does. It isn't obvious what it does until you know of it but then not many Unix utilities are. I considered "trsh" but that sounds like a shell. I considered "bin" but that already means binary. "throwout" or "thrwt" or chuck are longer and no more obvious in meaning.

I've tried to generalise my design to be: a way of moving stuff to a place where it can be easily restored from for only a limited time; so I like the way you can think of "can " to mean trash-can or "can" in the more general form as being a container. Of course "can" is a common english word so that's a bit annoying for this post but later on I can't see it as being a problem. The word also functions nicely as a verb: "I don't need that any more; can it"

Summary of Functionality

There are 3 scripts involved in this:
  • can - moves files to a trash can
  • ucan - restores files from a trash can
  • pcan - (purge can) this is usually executed daily, using cron, that permanently deletes files in the trash can if they have reached a certain age
Each time a file is canned it goes to the trash can (usually this is a hidden directory under the user's home dir). When files are canned they are done so in such a way as to maintain the location of the deleted file. If you delete "/foo/bar/file.txt" whilst logged in as the user "zim" the file will be moved to "/home/zim/.trash/foo/bar/file.txt", with the sub-dirs automatically created as necessary. You can then "find ~/.trash" in order to view all the files you've canned and see exactly where they used to be.

When you can files you may specify a --purge-after (-p) option to determine how long the file should hang around for. If this option is omitted the default value is used. You may specify a --purge-after of 0 or -1 to keep the file indefinitely. A new log file is created for each can operation containing the list of files that were canned, how long they should be kept for and when the can occurred. The log files will have the mode of 0400 and are stored in a dir ".canlogs" . A typical can log might look like this:

Code: Select all

2007-10-06 16:46:12,20
/home/zim/foo.txt
/home/zim/bar.txt
First is the date those files where deleted, then a colon, then how long they should stay around then on the new lines the deleted file paths.

If this should occur:

Code: Select all

$ touch foo
$ can foo
$ touch foo
$ can foo
can will fail saying that foo is already in the trash. You may force it with -f to perform the can overwriting the old version of foo in the process.


Configuration and Hooks

can, ucan and pcan are all configured by a file ".can" in the user's home directory, if this isn't found a system-wide file "/etc/can.conf" is used. The configuration files looks very much like ini files and are sectioned into blocks that may inherit from each other. There are only a handful of directives. A commented example file is shown below:

Code: Select all

####
# can.conf
#

    # the default configuration set
    [default]
    
        # where trashed files go
        trashcan=~/.trash
        
        # number of days trashed files remain in existance
        # set to 0 for no purge, this still permits user to use
        # purge in their invocation of can
        purge_after=45

        # the highest number a user can use when specifying 
        # their own --purge-after. 0 for no maximum
        max_purge_override=0

        # the lowest number a user can use when specifying 
        # their own --purge-after. 0 for no minimum
        min_purge_override=0
        
        # Hooks
        #
        # These are scripts that we executed either before (pre)
        # or after (post) can or ucan. These will be invoked once
        # for each file being moved by can or ucan unless otherwise stated
        
        
            # before can
                # $1 will be full filepath
                # $2 onwards are any --pre-args specified by the can invocation
                #
                # You may exit with a status number to communicate with can:
                #   0: continue with can - assumed
                #   1: abort the can and suppress execution of post_can_hook for this file
                #   2: abort the can and suppress execution of post_can_hook for this file
                #       and all others in this invocation of can
                #   3: do not can, purge right away
            pre_can_hook=''
                    
            # after can
                # $1 will be the full file path
                # $2 onwards are any --post-args specified by the can invocation
            post_can_hook=''

        
            # before ucan
                # $1 is the full file path where it was deleted from
                # $2 is the full destination file path (not necessarily the same as $1)
                # $3 onwards are any --pre-args specified
                #
                # You may exit with a status number to communicate with ucan:
                #   0: continue with ucan - assumed
                #   1: abort the ucan and suppress execution of post_ucan_hook for this file
                #   2: abort the ucan and suppress execution of post_ucan_hook 
                #      for this file and all others in this invocation of ucan
            pre_ucan_hook='' 
        
            # after ucan
                # $1 is the full file path where it was deleted from
                # $2 is the full destination file path (not necessarily the same as $1)
                # $3 onwards are any --post-args specified
            post_ucan_hook='' 
        
    # implements an alternate configuration set called 'foo' that 
    # inherits the options from upon default
    [foo : default]
    
        # deviation from default
        trashcan=/tmp/trash

    # another configuration not inheriting from anything
    [bar]

        # this will prevent this configuration set from being executed. You may wish 
        # to use this on default if you want to force your users to specify wish 
        # configuration to use
        disabled=1
As you can see in this configuration there are three blocks: default, foo and bar. You may specify which configuration you use in the -c option. Users can use this to direct their files to several trash cans if they wish.

Technical notes:
  • Because can create logs in ".canlogs" under the trash can dir you will be denied the ability to can anything called "/.canlogs"
  • If pcan encounters a file in a log that is not present in the trash can it will ignore it and carry on, assuming it has probably been restored
Usage

Code: Select all

Usage: can [OPTION]... FILE...
Recursively moves files or directories to a trash can

  -c --config=CONFIG      specifies which set of configuration directive should apply
  -f --force              cans even if something with same name and path already exists in trash can
  -v --verbose            show the files being canned
  -p --purge-after=DAYS   set the number of day the files will remain
                          in the trash before being purged (rm'd)
  -r --pre-args=ARGS      args to send to the pre_hock_script
  -o --post-args=ARGS     args to send to the post_hock_script
  -h --help               display this help text

Code: Select all

Usage: ucan [OPTION]... FILE... [DEST]
Restore files or directories from the trash can

FILEs and DEST are relative to . unless absolute

  -c --config=CONFIG      specifies which set of configuration directive should apply
  -r --pre-args=ARGS      args to send to the pre_hock_script
  -o --post-args=ARGS     args to send to the post_hock_script

Code: Select all

Usage: pcan [-c config|-a] [-e]
Deletes the current user's canned files that are due to be purged.

  -c --config=CONFIG      specifies which set of configuration directive should apply
  -a --all                 purges all users canned files
  -e --empty               completely empties the trash of everything even regardless

Posted: Sat Oct 06, 2007 4:32 pm
by Christopher
I think it would make more sense to make you command compatible with "rm" and "rmdir". Then alias "rm" to your new command.The new command would replicate all of the rm functionality but add undelete and empty trash.

Posted: Sun Oct 07, 2007 5:45 am
by Ollie Saunders
arborint wrote:I think it would make more sense to make you command compatible with "rm" and "rmdir". Then alias "rm" to your new command.The new command would replicate all of the rm functionality but add undelete and empty trash.
I can't remember where I read it but someone advised against this for several reasons:
  • You might want to permanently delete
  • Scripts and other programs will want to permanently delete and function happily doing this. Although there may be an advantage to storing the files deleted by scripts
  • You may come to rely on the behaviour and then find yourself burned when you start to use another system where rm really is rm.
So it's a difficult toss up. I do like the idea of completely overriding deleting behaviour for the whole system but that's not really possible. But I think you're right in that I should allow people the option. So that would mean making can a compatible replacement of rm primarily, hmm I think I could do that.