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

Hi guys, I hope someone can help me with this as I am a little stuck. I read a file and have place holders in the file. If there is a place holder specified then I replace it with its coresponding value from a hash. The code I have works fine when there is only one match on the line, the problems start with 2. First the code...
if ($loop_line =~/\[(.*)\]/) { my $replace = $1; $loop_line =~s/$replace/$replace_with/; }
So if the line contains static replace [something] here then something is replaced. That works no problem. If I have static replace [something] and [another] thing then I get into problems. Can anyone help and point me in the right direction it would be greatly appreciated. Thanks in advance.

Replies are listed 'Best First'.
Re: Replacing multiple matches
by edan (Curate) on Feb 16, 2004 at 11:36 UTC

    Something like this, perhaps:

    #!/usr/bin/perl use strict; use warnings; my %replace = ( Not => 'Just', Stupid => 'Perl', ); my $string = "[Not] Another [Stupid] Hacker"; $string =~ s/\[([^\]]+)\]/$replace{$1}/g; print $string, $/; __OUTPUT__ Just Another Perl Hacker
    --
    edan (formerly known as 3dan)

Re: Replacing multiple matches
by Roger (Parson) on Feb 16, 2004 at 11:36 UTC
    my %replacement = ( A => 'B', C => 'D', ); my $loop_line = '[A]A[C]C'; $loop_line =~ s/\[([^\]]+)\]/$replacement{$1}/g; print "$loop_line\n";
      Thank you all that replied. Especially so quickly. My problem is now solved - I really should master regex one day as it is often where I have trouble. Thanks again.......
Re: Replacing multiple matches
by Abigail-II (Bishop) on Feb 16, 2004 at 12:42 UTC
    There are two ways to address this problem, using lazy quantifiers, or using a restrictive class. The lazy quantifier uses .*?, the restrictive class uses [^]]*. One would expect the latter to be faster, and in general it is, but in this case, with fixed delimiters, Perls optimization makes it faster:
    #!/usr/bin/perl use strict; use warnings; use Benchmark; our $loop_line = "static replace [something] and [another] thing"; our %replace = (something => "SOMETHING", another => "ANOTHER"); my ($a, $b, $c); timethese -1 => { lazy => '$a = $loop_line; $a =~ s/\[(.*?)\]/$replace{$1}/ +g', restrictive => '$b = $loop_line; $b =~ s/\[([^]]*)\]/$replace{$1 +}/g', copy => '$c = $loop_line;', }; __END__ Benchmark: running copy, lazy, restrictive for at least 1 CPU seconds. +.. copy: 2 wallclock secs @ 3994936.79/s (n=4234633) lazy: 1 wallclock secs @ 121663.37/s (n= 122880) restrictive: 1 wallclock secs @ 117028.57/s (n= 122880)
    (Results slightly formatted for layout purposes). The copy clause shows that the impact of copying the string is small compared to the substitution.

    Abigail

Re: Replacing multiple matches
by Koosemose (Pilgrim) on Feb 16, 2004 at 12:05 UTC

    Well the first problem you are likely having is that with your regex, when you have something like static replace [something] and [another] thing it is grabbing the longest it can and therefore $replace ends up being something] and [another. A simple character class along the lines of [^]] (grabs everything but the closing bracket) instead of just . should do it

    The second problem I see is that when the replacement is done, it leaves the brackets there, so if you do iterate over it again, it will proceed to attempt to replace the replacements, which will likely not have any corresponding value in the hash, and therefore be undefined, and leave the spot empty. Adding escaped brackets into the the first half of the substitution will take care of that problem

    So, after all that here is my test case which should demonstrate one way to do this with a while loop and hashes as you first mentioned:

    $loop_line = "static replace [something] and [another]\n"; %replace_table = ('something','this','another','that'); print $loop_line; while ($loop_line =~ /\[([^]]*)\]/) { my $replace = $1; my $replacement = $replace_table{$1}; $loop_line =~ s/\[$replace\]/$replacement/; }; print $loop_line;

    update: bleh, took too long writing this, so I got beaten to it :( ahh well, if nothing else I got some practice on RegExs and Debugging :)

Re: Replacing multiple matches
by BUU (Prior) on Feb 16, 2004 at 22:03 UTC
Re: Replacing multiple matches
by Berik (Sexton) on Feb 16, 2004 at 11:27 UTC
    my @matches = $loop_line =~/\[(.*?)\]/; # Build a list of matches for ( @matches ) { $loop_line =~ s/\Q$_\E/$replace{$_}/; # \Q makes sure no strange t +hings happen }

    Hope this helps.
    There must be a faster way, but I can't think of one now.

    Update: Added ?, as our dear Abigail supposed.

    ---
    Berik
      Rubbish.
      #!/usr/bin/perl use strict; use warnings; my $loop_line = "static replace [something] and [another] thing"; my @matches = $loop_line =~/\[(.*)\]/; # Build a list of matches for ( @matches ) { $loop_line =~ s/\Q$_\E/PING/; } print $loop_line, "\n"; __END__ static replace [PING] thing
      The problem with the given code is the use of .*, which your solution doesn't solve at all. In fact, your solution doesn't address any problem. It's just adding slowness. It's worse than not giving an answer.

      Abigail

        /Off Topic/
        I don't see how this reply must have helped alongwor with his problem. Mine at least pointed him somewhat in the right direction. We all make errors, I even dare to say you make them too. But I thought that this website is all about learning, so you should make errors.

        Thanx.
        Update: I'm sorry for misreading, and thereby not suppling the answere. But I still do not agree with Abigail's reply.

        Hmm, I guess my post was rubbish after all, forget what I have posted here.
        Will do better next time.
        ---
        Berik