#!/usr/bin/perl -w
use strict;
use File::Find ();
sub wanted;
sub process_file ($@);
my $dirfile = shift; # First argument is the filename containing the list
# of directories.
my $pattern = shift; # Second arg is a perl RE containing the pattern to search
# for. Remember to single-quote it on the command line.
# Read in the @dirs array from $dirfile
#
# A NUL-separated file is best, just in case any of the directory names
# contained line-feeds. If you're certain that could never happen, a
# plain-text LF-separated file would do.
#
# BTW, you can easily generate a NUL-separated file from the shell with:
# printf "%s\0" dir1 dir2 dir3 dir4 $'dir\nwith\n3\nLFs' > dirs.txt
my @dirs=();
{
local $/="\0"; # delete this line if you want to use a LF-separated file.
# In that case, the { ... } block around the code from open to
# close is no longer needed. It's only there so it's possible
# to make a local change to the $/ aka $INPUT_RECORD_SEPARATOR
# variable.
open(DIRFILE,"<",$dirfile);
while(<DIRFILE>) {
chomp;
push @dirs, $_;
};
close(DIRFILE);
};
File::Find::find({wanted => \&wanted}, @dirs);
exit;
sub wanted {
my ($dev,$ino,$mode,$nlink,$uid,$gid);
(($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) && -f _ && process_file($_);
}
sub process_file ($@) {
# This function currently just greps for pattern in the filename passed to
# it. As the function name implies, it could be used to process the file
# in any way, not just grep it.
my $filename = shift;
# uncomment the return statement below to skip "binary" files.
# (note this is a workable but fairly crude test. Perl's File::MMagic
# module can be used to more accurately identify file types, using the
# same "magic" file databases as the /usr/bin/file command)
# return if -B $filename;
open(FILE,"<",$filename);
while(<FILE>) {
print "$filename:$_" if (m/$pattern/o) ;
};
close(FILE);
}
This uses perl
and perl's File::Find
module to do the same thing as your find ... -exec grep
.
There's nothing particularly interesting or special about this script except that the process_file
function can be very easily modified to do anything you want with or to the file - e.g. change owner or perms, delete it, rename it, insert or delete lines or whatever else you might want.
e.g. if you wanted to delete files which contain text matching the pattern, you could replace the process_file function with something like this:
sub process_file ($@) {
my $filename = shift;
my $found = 0;
# uncomment to skip "binary" files:
return if -B $filename;
open(FILE,"<",$filename);
while(<FILE>) {
if (m/$pattern/o) {
$found = 1;
last;
};
};
close(FILE);
unlink $filename if ($found);
}
It's also worth mentioning that the wanted
function in this script is currently only looking for regular files (the -f
test). Perl's stat
and lstat
functions provide access to all of the file metadata that find
can use to match files (uid, gid, perms, size, atime, mtime, etc) so the wanted
function can replicate ANY and all find predicates. see perldoc -f stat
and perldoc -f lstat
for details.
BTW, the script was generated initially by find2perl
, and then modified substantially to a) read in the list of directories from a file, and b) to do the grep in perl code rather than by forking grep
and c) add a lot of comments. Performance should be nearly identical to find ... -exec grep
because grep can't open files or do a regexp pattern match significantly faster than perl can. It may even be faster.
Also BTW, find2perl
used to be included with perl, but since perl 5.22 it was removed and can now be found on CPAN at find2perl
+
to end the-exec
instead of\;
, you wouldn't need the-H
option on the grep command (it's the default if multiple file args are used). More importantly, it would also run noticeably faster because it wouldn't need to fork one grep per file (instead it would run grep with as many filename args as will fit on the command line).find SubDir1 SubDir2 SubDir4 -type f -exec grep "desired text" {} +
find
only finds 1 regular file total, so-H
is still a good idea. But yes,-exec ... {} +
is much faster than\;
especially for lots of small files where startup overhead matters more.