Trash files from the OS X command line

Posted on March 9, 2010
Filed under Featured, Mac, Programming

trash picture I spend a lot of time in the Terminal on my computer — a lot of things are just better done with a command-line interface than in the GUI. When removing files via the command-line people usually just, well, remove them (with the rm command), but this means that they’ll be eschewing the Trash, one of the user-friendliest things (even relatively) modern operating systems have had to offer for a long time.

It’s obvious we need a trash command that we can use instead of rm when we’re not 101% sure we need to actually remove these files, like, right now. Here are a couple of existing solutions I’ve found, my impressions of them, and then my version at the bottom:

osxutils’ trash by Sveinbjörn Þórðarson

This is what I’ve used up until now. It’s a small Perl script that simply manually moves the specified files into the trash folder under the current user’s home directory:

# relevant code snippet:
...
while (-e "$ENV{HOME}/.Trash/$path_segs[$#path_segs]")
{
     $path_segs[$#path_segs] .= " copy $cnt";
}
system("mv '$ARGV[$argnum]' '$ENV{HOME}/.Trash/$path_segs[$#path_segs]'");
...

The biggest problem with this is that it moves all trashed files onto the same volume instead of using each volume’s own trash folders (this is not a good idea). It also deals with filename collisions manually, which is a bit volatile. The good thing about this is that it doesn’t follow “leaf” symbolic links, which is in my opinion the expected behaviour (what if I want to trash the link instead of its target?).

osx-trash by Dave Dribin

This one is a lot better. It’s a Ruby script that uses the Scripting Bridge to ask Finder to perform all the actual “heavy lifting” (e.g. moving the files to the trash) which means that each volume’s own trash folders are utilized properly and filename collisions are handled in a standard way:

# relevant code snippets:
...
finder = SBApplication.applicationWithBundleIdentifier("com.apple.Finder")
...
path = Pathname.new(file)
url = NSURL.fileURLWithPath(path.realpath.to_s)
item = finder.items.objectAtLocation(url)
item.delete
...

It also allows you to list all the files that are currently in the trash and empty it (normally or securely). The few minor negative things are the fact that it follows leaf symbolic links and the fact that when you try to trash multiple files you don’t have access rights for, Finder will pop up an authentication dialog separately for each file.

my trash

Due to the few things that I wanted the trash command to do differently, I wrote my own. In order to make sure that filename collisions are handled in the standard manner and that each volume’s trash folders are properly used (just like with Dave Dribin’s script) this one first tries to use the standard system API for trashing files, and if that fails due to the user not having the correct access rights, then asks Finder to trash those files (Finder can authenticate the current user as an administrator and move the files into that user’s trash — if you simply run this program as root, the files will be moved to root’s trash, which is not what we want):

// relevant code snippets:
...
// first try the standard API function (this should be
// the fastest way, and we get a nice status value
// as well):
//
FSRef fsRef;
FSPathMakeRefWithOptions(
    (const UInt8 *)[filePath fileSystemRepresentation],
    kFSPathMakeRefDoNotFollowLeafSymlink,
    &fsRef,
    NULL // Boolean *isDirectory
    );
OSStatus ret = FSMoveObjectToTrashSync(&fsRef, NULL, kFSFileOperationDefaultOptions);
...
// if no access rights, construct Apple event describing
// a "delete all items in this list" action and send it to Finder:
//
NSAppleEventDescriptor *descr = [NSAppleEventDescriptor
    descriptorWithDescriptorType:'furl'
    data:[[url absoluteString] dataUsingEncoding:NSUTF8StringEncoding]];
[urlListDescr insertDescriptor:descr atIndex:i++];
...
ProcessSerialNumber finderPSN = getFinderPSN();
NSAppleEventDescriptor *targetDesc = [NSAppleEventDescriptor
    descriptorWithDescriptorType:'psn '
    bytes:&finderPSN
    length:sizeof(finderPSN)];
NSAppleEventDescriptor *descriptor = [NSAppleEventDescriptor
    appleEventWithEventClass:'core'
    eventID:'delo'
    targetDescriptor:targetDesc
    returnID:kAutoGenerateReturnID
    transactionID:kAnyTransactionID];
[descriptor setDescriptor:urlListDescr forKeyword:'----'];
...
AESendMessage([descriptor aeDesc], &reply, kAEWaitReply, kAEDefaultTimeout);
...

The difference is that this one doesn’t follow leaf symlinks, and when deleting multiple files that you don’t have access rights for, Finder will only show one authentication dialog (I had to implement this kind of “manually” because I couldn’t find any way to accomplish it with the Scripting Bridge). I also copied the idea and implementation of emptying the trash and listing its contents via the Scripting Bridge from Dave’s script. This trash is written in (Objective-)C, for all those extra milliseconds that are so goddamn important when waiting for files to be trashed.

You can go through my trash here.

Comments

9 Responses to “Trash files from the OS X command line”

Show/hide comments & reply form: