Following on from my post yesterday with three examples of using Automator to create Services, and some good suggestions in the comments, I spent some time this afternoon making the script in the third of those examples a little more efficient, and a lot more robust.

The Service I optimised was the one to strip keywords from image files. This Service assumes that both Growl and EXIFTool are installed, and that you’re running OS X 10.6 Snow Leopard or later.

Lets start with a reminder of where we left things yesterday. First, here’s a screenshot of the Workflow as a whole:

The Original Workflow

The only thing we’ll be changing today is the Perl code inside the Run Shell Script Action, so lets have a closer look at that code:

foreach (@ARGV) {
	`exiftool -Subject="" -Keywords="" $_`;
	$deleteCommand = "rm ".$_."_original";
	`$deleteCommand`;
}

So, what does this code do?

That array @ARGV contains the list of files to be edited, one filename per element in the array. That array is looped through, and for each element, EXIFTool is called on that file, and then the duplicate generate by EXIFTool is deleted.

Sounds simple, what’s the problem? The single biggest flaw is that the file names are not escaped in any way before using Perl to send them to the command-line. This means that if you include a space or any other character that has a special meaning to the shell, the Action will not work.

As well as that flaw, there are two inefficiencies, in the above code. First, rather than asking EXIFTools never to create the duplicates, we instead delete them afterwards. And second, we shell out to EXIFTool once for each file, rather than invoking EXIFTool once and passing it all the files in one go.

You can get around most of the filename problems by simply surrounding each filename in quotes before passing it to the shell. That gets rid of the problem with spaces, as well as with almost all the other special characters. The few special characters that still do cause a problem when quoted need to be escaped by putting a \ character in front of them. I wasn’t able to find a definitive list of trouble-characters online, but after quite a bit of testing I think I have a pretty exhaustive list, namely ", `, $, and \.

As for optimisations, the -overwrite_original flag tells EXIFTool not to save the duplicate files, and once the filenames have been cleaned and quoted it’s easy to just invoke EXIFTool once with all the filenames.

Here’s my updated code:

foreach (@ARGV){ s/([`"\\\$])/\\$1/g }
my $files = '"'.join('" "', @ARGV).'"';
`exiftool -Subject="" -Keywords="" -overwrite_original $files`;

It looks short, but it does a lot in those few lines (one reason I just love Perl 🙂 ). Lets go through it line-by-line.

The first line loops through the entire array looking for the characters that need to be escaped, and pre-pends the escape character (\) before each instance of each of these characters using Perl’s substitution functionality. This is a very short-hand way of witting this loop, you might find it easier to understand in this entirely equivalent long-hand version:

foreach my $fileName (@ARGV){
	$fileName ~= s/([`"\\\$])/\\$1/g;
}

The second line turns the sanitised array of filenames into a single string that contains a quoted list of the files. This is done by starting the string with a ", then using the join function to copy in all the filenames with " " between each pair, before finally ending the string with a final ".

Finally, we shell out to EXIFTool using the -overwrite_original flag to suppress the creation of the duplicates, and giving it all the files in one go using the quoted list we just generated.

I think this Service should be robust enough for real-world use now, but if you see anything I’ve missed, please post a comment.

Download