I have written a program where the user inputs a regular expression and a replacement string, and the program will search through a set of files and do the replacements.
The user is allowed to use backreferences in the replacement string (to refer to capture groups in the regex).
The part of the program I would like you to consider is the sub routine substitute_regex_backref
below that searches a input string (for example the contents of a file) and does the replacements:
#! /usr/bin/env perl
use feature qw(say);
use strict;
use warnings;
my $old_str = '$1B<$ <hello> $> aba B<$ <kk> $>$1';
my $str = $old_str;
my $regex = qr'B<\$ <(.*?)> \$>';
my $replace = 'I<\$1$1\$1>';
(my $cnt, $str) = substitute_regex_backref( $str, $regex, $replace );
say "Old string: '$old_str'";
say "Number of replacements: $cnt";
say "New string: '$str'";
exit;
#
#
# ($num_substitutions, $new_str) =
# substitute_regex_backref( $str, $regex, $replace )
#
#
# This sub routine is based on a stackoverflow.com answer
# by username Kent Fredric, see http://stackoverflow.com/a/392649/2173773
#
# Replace all occurences of $regex in $str with $replace.
# Returns number of replacements and the new string.
#
# The $regex input is assumed to be a regex quoted string. For example:
# my $regex = qr/A simple (\w+) example/;
#
# The replacement string $replace is allowed to have backreferences:
# Example: $replace = "a $1 b"
# "$1" is here treated as a backreference. It corresponds to capture group number 1
# in the $regex string.
#
# The replacement string $replace can also have backslash escaped dollar signs,
# Example: $replace = "\$1 a $1 b"
# Such escaped dollar signs should be replaced by
# a literal '$' (and not treated as a backreference)
# Note: This subroutine was written to avoid using the ee modifier technique:
# $var =~ s/$find/$replace/ee;
# which has sequrity risks if the $replace string comes from user input.
#
sub substitute_regex_backref {
my ( $str, $regex, $replace ) = @_;
# First obtain an array @m of matches
my @m = $str =~ /$regex/g;
if (@m == 0) {
return (0, $str);
}
my $special_character_seq = "\x41\x42\x43\x44";
# Remove any dollar signs from $str
$str =~ s/\$/$special_character_seq/g;
# If $regex contain escaped dollar signs (to be treated as
# literal dollar signs), we need to replace
# them with $special_character_seq since we have removed all
# dollar signs in the previous line
$regex =~ s/\\\$/$special_character_seq/g;
# Do the replacement, but dollar signs in the $replace variable
# are left as literal dollar signs
$str =~ s/$regex/$replace/g;
# Replace backslash escaped dollar signs with special string
$str =~ s/\\\$/$special_character_seq/g;
# use "reverse" function to cope with mixed one- and two-digit back references
# For example, $12 should be dealt with before $1, in order to avoid confusion.
for( reverse 0 .. $#m ){
my $n = $_ + 1;
my $val = $m[$_];
# Replace "$n" with the value of capture group
$str =~ s/\$$n/$val/g;
}
# Reinsert all literal dollar signs:
$str =~ s/$special_character_seq/\$/g ;
return (@m + 0, $str);
}
Any comments and suggestions are appreciated. I am especially concerned with the use of the $special_character_seq
variable, if it is necessary or how to avoid using it..