Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I'm trying to take each line of input, run a substitution according to user-specified "before" and "after" patterns, and output the result - it's for use in a typical "Perl rename"-type script. Because the patterns are user-specified, I end up doing basically $input =~ s/$old/$new/ - however, this seems to break subexpression references on the replacement side.

Demonstration code:

#!/usr/bin/perl -w use strict; my ($oldpat, $newpat); my ($oldname, $newname); $oldname = 'foo-64-bar'; # would normally be read from stdin # basic substitution $newname = $oldname; $newname =~ s/-([0-9]+)-/_$1_/; print "static patterns: $oldname becomes $newname\n"; # the same, but with the patterns in variables $oldpat = '-([0-9]+)-'; $newpat = '_$1_'; $newname = $oldname; $newname =~ s/$oldpat/$newpat/e; print "variable patterns: $oldname becomes $newname\n";

As far as I can see, this should perform the same substitution in both cases, but it produces this:

static patterns: foo-64-bar becomes foo_64_bar variable patterns: foo-64-bar becomes foo_$1_bar

I've checked the perlop and perlre manpages, as well as any likely-looking threads here, but didn't see anything suggesting that the contents of a scalar used as a replacement pattern are somehow special, or that $var references in such a string won't be interpolated (unless you use single quotes as the pattern delimiter, which I'm not). I've tried wrapping the subsitution in an eval{}, as well as leaving off the /e modifier, but neither made any difference.

I'm sure I'm missing something obvious. Anyone care to tell me what it might be?

Replies are listed 'Best First'.
Re: s/// ignoring subexpressions with variable replacement patterns
by mattriff (Chaplain) on Oct 04, 2002 at 15:10 UTC
    The problem is here:

    $newpat = '_$1_';

    That needs to be in double-quotes, or it gets interpreted literally.

    That's the problem with the sample code, at least. It sounds like your production code might be different, since you mention the patterns are user-supplied. Do you have a sample of that?

    - Matt Riffle

      No, that was indeed the problem. I was under the impression that double-quoting the patterns at that point would cause $vars to be interpolated immediately, rather than in the substitution - but thinking about it, $1 and the like can never be regular variables anyway.

      My mistake; thank you.

      If you just change those single quotes to double quotes, then the value of $1 from the previous pattern will be interpolated before the next substitution ever happens. If you really want the "variable pattern" to behave the same as the "static pattern", you may want to do something like this:
      $newname = 'foo-64-bar'; $oldpat = '-([0-9]+0-'; $newpat = q/'_'.$1.'_'/; $newname =~ s/$oldpat/$newpat/ee; print "variable patterns: $oldname becomes $newname\n";
      That's two /e modifiers, and it's not particularly pretty, but it works. I'm sure there's probably a better way, though. :-)

      -- Mike

      --
      just,my${.02}

Re: s/// ignoring subexpressions with variable replacement patterns
by Joost (Canon) on Oct 04, 2002 at 15:23 UTC
    $oldpat = '-([0-9]+)-'; $newpat = '"_$1_"'; # note double quotes in single quotes $newname = $oldname; $newname =~ s/$oldpat/$newpat/ee; # note double ee
    Works. But you'd better be very careful about what replacement strings are entered!

    It will eval '"_$1_"' to mean "_$1_" and then replace the match with what that expression (not string) returns.

    Anyway, contrary to what mattriff said, just putting the $newpat in double quotes will interpolate $1 before the match is run, so that won't work.

    -- Joost downtime n. The period during which a system is error-free and immune from user input.
      It worked in his specific test code, anyway.

      Unless, of course, you take out the other match, where $1 is getting set. I see what I did now. Sorry. - Matt Riffle