Although this can be done from the command line, sometimes you just need an easily used script that provides a slightly more useful output. With that in mind, here is a perl solution with a friendly way out for anyone who comes across this issue.
#!/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 (ie 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}; # ie $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; }
simpleuser
source share