The code below uses Perl's in-place editing feature to modify only those files that need changing and leaves backups of the originals. For example, if it updates foo.php, the original will be in foo.php.bak.
We begin with typical front matter. We'll use the File::Find module to search for HTML and PHP files at any depth, starting by default in the current directory or in all directories named on the command line.
#! /usr/bin/perl
use warnings;
use strict;
use File::Find;
sub usage { "Usage: $0 [ directory .. ]\n" }
In read_filelist, we get the list of transformations use them to generate two bits of code:
- a compiled regex that matches any line containing at least one old filename, and
- a compiled sub that replaces old names with new.
Note the use of \b which matches only at word boundaries and quotemeta, which escapes any regex metacharacters. These constrain the replacements to only literal occurrences of the old filenames.
It's a bit lengthy, so pardon the scrollbar.
sub read_filelist {
my $path = "filelist.txt";
open my $fh, "<", $path
or die "$0: open $path: $!";
my $code = q[
sub {
while (<>) {
];
my @old;
my $errors;
while (<$fh>) {
next if $. == 1 && /OldName.*NewName/;
my($old,$new) = split;
unless (defined($old) && defined($new)) {
warn "$0: $path:$.: missing filename\n";
++$errors;
}
my($qmold,$qmnew) = map quotemeta($_) => $old, $new;
$code .= " s/\\b$qmold\\b/$qmnew/gi;\n";
push @old, $old;
}
die "$0: will not continue\n" if $errors;
$code .= q[ print;
}
}];
local $@;
print $code, "\n";
my $sub = eval $code;
die "$0: should not happen: $@" if $@;
my $pattern = '\b(' .
join("|", map quotemeta, @old) .
')\b';
#print $pattern, "\n";
my $regex = eval { qr/$pattern/oi };
die "$0: should not happen: $@" if $@;
($regex, $sub);
}
Here we remember the root directories to begin searching and read filelist.txt. Perl's in-place editing requires the files to be updated to be in the special @ARGV array.
my @dirs = @ARGV ? @ARGV : ".";
my($has_old,$replace) = read_filelist;
The sub needs_update is the worker that File::Find::find uses to check whether a given file should be modified. We could place all PHP and HTML files in @ARGV, but in cases when the code modifies nothing, you'd still get backup files for everything.
sub needs_update {
return unless /\.(?:php|html)$/i;
open my $fh, "<", $_
or warn("$0: open $_: $!"), return;
while (<$fh>) {
if (/$has_old/) {
push @ARGV, $File::Find::name;
return;
}
}
},
For the main event, we clear @ARGV, use needs_update to add the appropriate PHP and HTML files, and unleash the compiled sub on them.
# set up in-place editing
@ARGV = ();
find \&needs_update, @dirs;
die "$0: nothing to do!\n" unless @ARGV;
$^I = ".bak";
$replace->();
__END__
A couple of notes:
- The code above attempts to make all replacements in the order given in
filelist.txt.
- It makes no attempt to limit scope to
<link> and <script> elements. Doing this correctly requires an HTML parser. If names of Javascript sources in filelist.txt should not be replaced sometimes, I can update this answer with the extra machinery, but doing would add even more code.
On Windows, paste the code (minus my running commentary) into a file with an extension of .pl, and then run it as
C:\> fixjs.pl
to patch HTML and PHP files beneath the current directory. To process one or more directories elsewhere, run
C:\> fixjs.pl D:\some\dir E:\another