Wednesday, December 30, 2015

Perl Recursive Directory Search: How-To

Lets face it: recursion can be hard.

But sometimes you just need a quick answer for something that surely has already been cracked.  If that's you, here's your quick answer: find2perl.  Use it. Love it, and get your job done.

If you're looking for a more in depth treatment on recursive algorithms, then you should start with C.  Specifically, here: Lesson 16: Recursion in C

But for the problem at hand, I'll first refer you to our trusty tool called "find" (see WikiPedia).

Typical syntax for the find command could look like the following:
$ find . -name bin -type d -exec echo {} \;
That command will find all directories called 'bin' and print them, as follows:
./.cpan/build/Crypt-RC4-2.02-ptGTQ1/blib/bin
./.cpan/build/Digest-Perl-MD5-1.9-Uenq6s/blib/bin
./.cpan/build/ExtUtils-MakeMaker-7.04-yzvWOc/bin
The -exec switch is not necessary: it's provided here only as an example.

Once you've constructed a find command that fetches the files you're looking for, then you're ready to convert it into a PERL script and begin the process of hacking it into something useful.  Usage is straight forward:

$ find2perl . -name bin -type d -exec echo {} \; > myFindScript.pl

You'll be presented with output like the following.  The rest is up to you.

#! /usr/bin/perl -w
    eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
        if 0; #$running_under_some_shell

use strict;
use File::Find ();

# Set the variable $File::Find::dont_use_nlink if you're using AFS,
# since AFS cheats.

# for the convenience of &wanted calls, including -eval statements:
use vars qw/*name *dir *prune/;
*name   = *File::Find::name;
*dir    = *File::Find::dir;
*prune  = *File::Find::prune;

sub wanted;
sub doexec ($@);


use Cwd ();
my $cwd = Cwd::cwd();


# Traverse desired filesystems
File::Find::find({wanted => \&wanted}, '.');
exit;


sub wanted {
    my ($dev,$ino,$mode,$nlink,$uid,$gid);

    /^bin\z/s &&
    (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) &&
    -d _ &&
    doexec(0, 'echo','{}');
}


sub doexec ($@) {
    my $ok = shift;
    my @command = @_; # copy so we don't try to s/// aliases to constants
    for my $word (@command)
        { $word =~ s#{}#$name#g }
    if ($ok) {
        my $old = select(STDOUT);
        $| = 1;
        print "@command";
        select($old);
        return 0 unless <STDIN> =~ /^y/;
    }  
    chdir $cwd; #sigh
    system @command;
    chdir $File::Find::dir;
    return !$?;
}