package FastGlob;
=head1 NAME
FastGlob - A faster glob() implementation
=head1 SYNOPSIS
use FastGlob qw(glob);
@list = &glob('*.c');
=head1 DESCRIPTION
This module implements globbing in perl, rather than forking a csh.
This is faster than the built-in glob() call, and more robust (on
many platforms, csh chokes on C<echo *> if too many files are in the
directory.)
There are several module-local variables that can be set for
alternate environments, they are listed below with their (UNIX-ish)
defaults.
$FastGlob::dirsep = '/'; # directory path separator
$FastGlob::rootpat = '\A\Z'; # root directory prefix pattern
$FastGlob::curdir = '.'; # name of current directory in dir
$FastGlob::parentdir = '..'; # name of parent directory in dir
$FastGlob::hidedotfiles = 1; # hide filenames starting with .
So for MS-DOS for example, you could set these to:
$FastGlob::dirsep = '\\'; # directory path separator
$FastGlob::rootpat = '[A-Z]:'; # <Drive letter><colon> pattern
$FastGlob::curdir = '.'; # name of current directory in dir
$FastGlob::parentdir = '..'; # name of parent directory in dir
$FastGlob::hidedotfiles = 0; # hide filenames starting with .
And for MacOS to:
$FastGlob::dirsep = ':'; # directory path separator
$FastGlob::rootpat = '\A\Z'; # root directory prefix pattern
$FastGlob::curdir = '.'; # name of current directory in dir
$FastGlob::parentdir = '..'; # name of parent directory in dir
$FastGlob::hidedotfiles = 0; # hide filenames starting with .
=head1 INSTALLATION
Copy this module to the Perl 5 Library directory.
=head1 COPYRIGHT
Copyright (c) 1997 Marc Mengel. All rights reserved.
This library is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=head1 AUTHOR
Marc Mengel E<lt>F<
[email protected]>E<gt>
=cut
use Exporter ();
@ISA = qw(Exporter);
@EXPORT = qw(&glob);
@EXPORT_OK = qw(dirsep rootpat curdir parentidr hidedotfiles);
use strict; # be good
no strict 'vars'; # ... but not *that* good
#
# recursively wildcard expand a list of strings
#
# platform specifics
$dirsep = '/';
$rootpat= '\A\Z';
$curdir = '.';
$parentdir = '..';
$hidedotfiles = 1;
$verbose = 0;
sub glob {
my($string) = $_[0];
my(@comps,@res,@list,$re);
# check for and do tilde expansion
if ( $string =~ /^\~([^${dirsep}]*)/ ) {
if ( $1 eq "" ) {
@list = getpwuid($<);
} else {
@list = getpwnam($1);
}
$string =~ s/^\~([^${dirsep}]*)/$list[7]/;
}
# if there's no wildcards, just return it
if ( ! $string =~ /(^|[^\\])[*?\[\]{}]/ ) {
return ($string);
}
# Make the glob into a regexp
# escape + , and |
$re = $string;
$re =~ s/[+.|]/\\$&/go;
# handle * and ?
$re =~ s/(\A|[^\\])\*/$1.*/go;
$re =~ s/(\A|[^\\])\?/$1./go;
# deal with {xxx,yyy,zzz} -> (xxx|yyy|zzz) (while works for nested...)
while ( $re =~ /\{([^\{\}]*)\}/) {
@altlist = split(',',$1);
$re =~ s/\{([^\{\}]*)\}/"(" . join("|", @altlist) . ")"/e;
}
# deal with dot files
if ( $hidedotfiles ) {
$re =~ s%(\A|${dirsep})\.\*%${1}([^.].*)?%go;
$re =~ s%(\A|${dirsep})\.%${1}[^.]?%go;
}
# debugging
print "regexp is $re\n" if ($verbose);
# now split it into directory components
@comps = split( ${dirsep}, ${re} );
if ( $comps[0] =~ /${rootpat}/ ) {
shift(@comps);
@res = &recurseglob( "$&$dirsep", "$&$dirsep" , @comps );
} else {
@res = &recurseglob( $curdir, '' , @comps );
}
return sort(@res);
}
sub recurseglob {
my($dir, $dirname, @comps) = @_;
my(@res) = ();
my($re, $anymatches, @names, $string);
if ( $#comps == -1 ) {
# boottom of recursion, just return the path
chop($dirname); # always has gratiutous trailning slash
@res = ($dirname);
} else {
$re = '\A' . shift(@comps) . '\Z';
# slurp in the directory
opendir(HANDLE, $dir);
@names = readdir(HANDLE);
closedir(HANDLE);
# look for matches, and if you find one, glob the rest of the
# components. We eval the loop so the regexp gets compiled in,
# making searches on large directories faster.
$anymatches = 0;
$string = <<EOF;
foreach \$name (\@names) {
if ( \$name =~ /$re/o ) {
if ( \$name ne "$curdir" && \$name ne "$parentdir") {
unshift(\@res, &recurseglob( "$dir$dirsep\$name",
"$dirname\$name$dirsep",
\@comps ));
} elsif ( $#comps == -1 ) {
unshift(\@res, "$dirname\$name" );
}
\$anymatches = 1;
}
}
EOF
print "evaling: $string\n" if ($verbose);
eval $string;
}
return @res;
}
sub globtest {
my(@t0, @t1, $udiffm, $sdiffm, $udiffg, $sdiffg, @list);
local($,);
$, = " ";
while (<>) {
chomp;
@t0 = times();
@list = &glob($_);
@t1 = times();
$udiffm = ($t1[0] + $t1[2]) - ($t0[0] + $t0[2]);
$sdiffm = ($t1[1] + $t1[3]) - ($t0[1] + $t0[3]);
print "@list\n";
@t0 = times();
@list = glob($_);
@t1 = times();
$udiffg = ($t1[0] + $t1[2]) - ($t0[0] + $t0[2]);
$sdiffg = ($t1[1] + $t1[3]) - ($t0[1] + $t0[3]);
print "@list\n";
print "mine: [${udiffm}u\t${sdiffm}s]\n";
print "glob: [${udiffg}u\t${sdiffg}s]\n";
}
}
1;
__END__