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