コマンド ラインから実行することもできますが、もう少し有用な出力を提供する簡単に使用できるスクリプトが必要な場合もあります。そのことを念頭に置いて、この質問に出くわした人にとってわかりやすい出力を備えた perl ソリューションを次に示します。
#!/usr/bin/env perl5.8.3
# subst [-v] [-f] "re/string to find" "string to replace" -- list of files
# optional -v flag shows each line with replacement, must be 1st arg to script
# optional -f flag says to disable regexp functionality and make the strings match exactly
# replacement string may include back references ($1, $2, etc) to items in "string to find" if they are surrounded by grouping parenthesis
use strict;
use warnings;
use List::Util;
use IO::File;
use Fcntl;
use Getopt::Long qw(GetOptions);
my $verbose = 0;
my $fixed = 0;
GetOptions("v" => \$verbose,
"f" => \$fixed);
my $find = shift @ARGV;
my $replace = shift @ARGV;
die "Error: missing 1st arg, string to find\n" if not defined $find;
die "Error: missing 2nd arg, string to replace with\n" if not defined $replace;
die "No files were specified\n" if @ARGV == 0;
# open a temp file for writing changes to
my $TEMP = IO::File->new_tmpfile;
if (not defined $TEMP)
{
print STDERR "ERROR: failed to create temp file: $!\n";
exit 1;
}
# Fix max file name width for printing
my $fwidth = List::Util::max map { length $_ } @ARGV;
# Process each file
my $unchanged = 0;
my $changed = 0;
foreach my $file (@ARGV)
{
if (open(my $FILE, '<', $file))
{
# Reset temp file
seek $TEMP, 0, SEEK_SET or die "ERROR: seek in temp file failed: $!";
truncate $TEMP, 0 or die "ERROR: truncate of temp file failed: $!";
# go through the file, replacing strings
my $changes = 0;
while(defined(my $line = <$FILE>))
{
if ($line =~ m/$find/g)
{
print "-" . $line if $verbose;
print "\n" if $verbose and $line !~ m/\n$/;
if ($fixed)
{
my $index = index($line, $find);
substr($line, $index, length($find)) = $replace;
}
else
{
$line =~ s/$find/replacebackrefs($replace)/eg;
}
$changes++;
print "+" . $line if $verbose;
print "\n" if $verbose and $line !~ m/\n$/;
}
print $TEMP $line;
}
close $FILE;
if ($changes == 0)
{
$unchanged++;
unlink("/tmp/subst$$");
next;
}
# Move new contents into old file
$changed++;
printf "%*s - %3d changes\n", -$fwidth, $file, $changes;
seek $TEMP, 0, SEEK_SET or die "ERROR: rewind of temp file failed: $!";
open $FILE, '>', $file or die "ERROR: failed to re-write $file: $!\n";
while (<$TEMP>) { print $FILE $_ }
close $FILE;
print "\n" if $verbose;
}
else
{
print STDERR "Error opening $file: $!\n";
}
}
close $TEMP;
print "\n";
print "$changed files changed, $unchanged files unchanged\n";
exit 0;
sub replacebackrefs
{
# 1st/only argument is the text matched
my $matchedtext = shift @_;
my @backref;
# @- is a dynamic variable that holds the offsets of submatches in
# the currently active dynamic scope (i.e. within each regexp
# match), corresponding to grouping parentheses. We use the count
# of entrees in @- to determine how many matches there were and
# store them into an array. Note that @- index [0] is not
# interesting to us because it has a special meaning (see man
# perlvar for @-)\, and that backrefs start with $1 not $0.
# We cannot do the actual replacement within this loop.
do
{
no strict 'refs'; # turn of warnings of dynamic variables
foreach my $matchnum (1 .. $#-)
{
$backref[$matchnum] = ${$matchnum}; # i.e. $1 or $2 ...
}
} while(0);
# now actually replace each back reference in the matched text
# with the saved submatches.
$matchedtext =~ s/\$(\d+)/$backref[$1]/g;
# return a scalar string to actually use as the replacement text,
# with all the backreferences in the matched text replaced with
# their submatch text.
return $matchedtext;
}