Create aliases to icons via a Perl script

Jun 24, '08 07:30:00AM

Contributed by: Skeeve

Often I read the question "Where are all the icons on my Mac?" Here is the answer, in the form of a Perl script. Just copy and paste the below code into a new file, save it as something (findOSXicons) to a directory in your path, and make it executable (chmod 755 findOSXicons). Then use the usual techniques to start the script (i.e. calling it using the full or relative path). Here's the code:

#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Long;
use Pod::Usage;
use File::Find;
use File::Basename;

# here we store the results
my $target=$ENV{'HOME'} . '/Desktop/OSX-Icons';

# you may change the extensions to use
my @extensions= qw( icns );

###### POD #################################################################{{{

=head1 NAME

findOSXicons - recursively search for all icns files in your system

=head1 SYNOPSIS

B<findOSXicons>
[B<-target> I<targetdirectory>]
[B<-extension> I<ext>]...
[F<startpath>...]

B<findOSXicons>
B<-help>

B<findOSXicons>
B<-man>

=head1 DESCRIPTION

B<findOSXicons> recursively traverses the startpaths (or / if none given)
and creates a directory structure containing links to each and every icon
(files ending with .icns) found.

=head1 EXAMPLE

findOSXicons -target ~/Desktop/MyIcons -extension gif -extension icns ~

=head1 OPTIONS

=over 5

=item B<-extension> I<ext>

As a default the extension I<icns> is searched. with this option you
can specify extensions to use instead. If you give this option several
times, each extension is searched

=item B<-target> I<targetdirectory>

As a default the directory OSX-Icons is created on your desktop. With this
option you can specify which output directory to create instead.

=item B<-help>

Short help

=item B<-man>

Long help

=back

=head1 BUGS

None yet except that you get several warnings for directories you are not
allowed to acces. Use 2E<gt>/dev/null to suppress them.

=head1 ACKNOLEDGEMENTS

findOSXicons was entirely written by Skeeve
(findOSXiconsZ<>.Z<>questionsZ<>.Z<>skeeveZ<>@Z<>xoxyZ<>.Z<>net)

It was inspired by several user requests in the german apple forum at
L<http://www.apfeltalk.de/forum>.

=cut

###### END: POD ############################################################}}}

##### commandline options ##################################################{{{
# defaults
&help unless GetOptions(
	"help"        => \&help,
	"man"         => \&man,
	'target=s'    => \$target,
	'extension=s' => \@extensions,
);
sub help { exit pod2usage( verbose => 1 ); };
sub man { exit pod2usage( verbose => 2 ); };
##### end commandline options ##############################################}}}


# append a trailing slash
$target=~ s#([^/])$#/#;
# and calculate the length
my $target_length= length($target);

# check which extensions to find
die "No extension specified\n" unless scalar @extensions;
my $extensions= join ('|',map quotemeta,@extensions);
$extensions= qr/(\.(?:$extensions))$/;


# Statistics etc.
my @removed;        # list of removed icons
my $count_old= 0;   # icons we know of
my $count_new= 0;   # new icons
my $count_oapp= 0;  # application icons we know of
my $count_napp= 0;  # new application icons

# create the target directory unless it exists
mkdir $target or die "Can't create $target: $!\n" unless -d $target;

# hash of files already linked
my %stat;
# build the hash.
find ( {
	wanted => sub {
		# get the link target
		my $lnk= readlink $File::Find::name;
		# No link target? Then skip this.
		return unless defined $lnk;
		# Does the target exist?
		if ( -e $lnk ) {
			# build the hash
			++$stat{$lnk};
			# do some statistics
			++$count_old;
			# is it an applicationicon?
			++$count_oapp if $lnk=~ m#/[^/]+\.app/#;
		}
		else {
			# target is missing so remove the link
			unlink $File::Find::name;
			# and remeber that it was removed
			push(@removed, $lnk);
		}
	},
	no_chdir => 1,
}, $target);


# now find
find( {
	wanted => sub {
		# just files
		return unless -f $File::Find::name;
		
		# need to get the basename (no_chdir!)
		my $fname= basename $_;
		
		# correct extension?
		return unless $fname=~ s#$extensions##;
		my $ext= $1;
		
		# we don't want our target to be checked
		return if substr($File::Find::dir, 0, $target_length) eq $target;
		
		# check whether or not we already know of this file
		return if $stat{$File::Find::name}++;
		++$count_new;
		
		# create the subdir in the target dir
		my $link_dir= $target;
		# 2 possibilities
		if ( m#/([^/]+)\.app/# ) { # is it an Application's icon?
			# we put it into the Applications-subdirectory
			$link_dir.= 'Applications';
			mkdir $link_dir or die "Can't create $link_dir: $!\n" unless -d $link_dir;
			# into a directory for the application
			$link_dir.= "/$1";
			++$count_napp;
		}
		else {
			# we put it into an "alphabetical" subdirectory
			$link_dir.=  uc substr($fname,0,1);
		}
		# create the directory
		mkdir $link_dir or die "Can't create $link_dir: $!\n" unless -d $link_dir;
		
		# now calculate the filename
		my $link_name= $link_dir . '/' . $fname;
		my $count= '';
		--$count while( -e "$link_name$count$ext");
		# now link;
		symlink $File::Find::name, "$link_name$count$ext";
	},
	no_chdir => 1,
	follow => 0,
}, scalar @ARGV ? @ARGV : '/');

# Statistics
my $icon_count= $count_old + $count_new;
my $app_count= $count_oapp + $count_napp;
my $else_count= $icon_count-$app_count;
my $else_new= $count_new - $count_napp;
my $else_old= $count_old - $count_oapp;
my $miss_count= scalar @removed || 'none';
print <<REPORT;
      All icons known: $icon_count
    Application icons: $app_count
Non-application icons: $else_count
REPORT

print <<REPORT if $count_new;

      New icons found: $count_new
    Application icons: $count_napp
Non-application icons: $else_new

            Old icons: $count_old
    Application icons: $count_oapp
Non-application icons: $else_old
REPORT

print <<REPORT;

Icons removed since last run: $miss_count
REPORT

print "$_\n" foreach (sort @removed);
By default, this script will copy a link (kind of an alias) to each and every icon in the system into a folder on the desktop in a nice hierarchy.

Each icon belonging to an Application will be linked from a folder:

Desktop » OSX-Icons » Applications » application_name » icon_name

Each icon not belonging to an application wil be linked from a folder:

Desktop » OSX-Icons » letter » iconname

where letter is the first character of the icon's name. But you can do more with it! Instead of just starting findOSXicons from the Terminal, you can also tell it to:

Just enter findOSXicons -help or findOSXicons -man for instructions.

[robg adds: I tested this, and it works ... but it can take a *long* time to run if you've got a ton of files on your machine. If you'd rather not copy, paste, and make executable, I've put a zipped ready-to-use version on the macosxhints server.]

Comments (18)


Mac OS X Hints
http://hints.macworld.com/article.php?story=20080624065635470