0

This is driving me nuts. Help?

It looks to me like inside a Perl foreach loop, variables outside of a replacement pattern change as expected, but a variable inside a replacement pattern gets "stuck". It's almost as though when perl encounters a s/// replacement pattern inside a foreach code block, it interpolates the replacement pattern contents the first time through the loop and never again.

Here is some test code:

#!/usr/bin/perl

@replacements=("a", "b", "c");
@input=("xxletterxx");

foreach $replacement (@replacements) {
        foreach $line (@input) {
                $line=~s/xxletterxx/$replacement/g;
                print "R: $replacement\n";
                print "L: $line\n";
        }
}

I think it should print this:

R: a   
L: a   
R: b   
L: b   
R: c   
L: c

...but instead it prints this:

R: a  
L: a  
R: b  
L: a  <--- Why isn't this 'b'?  
R: c  
L: a  <--- Why isn't this 'c'?  

Notice how the "L" value is still "a" inside the replacement pattern even though elsewhere in the code block it's changing with the members of @replacements?

Why is that?

I feel like Perl is suddenly broken, or I've lost my mind.

It almost seems like this DOS behavior is occurring in perl.

Community
  • 1
  • 1
chewtoy
  • 109
  • 1

3 Answers3

6

This loop control:

foreach $line (@input)

aliases $line to each element of @input so the first time you do the substitution, you've changed not only $line but $input[0] as well. After that, the xxletterxx is gone, so no more substitutions are happening because the /xxletterxx/ is not matching.

To make it work the way you wanted, you need to break the aliasing, either by iterating over a clone of the @input array:

foreach $line (@{[@input]})

or by copying of $line to another variable before modifying it, treating $line as readonly in the body of the loop:

foreach $line (@input) {
        $modifiedline = $line;
        $modifiedline=~s/xxletterxx/$replacement/g;
        print "R: $replacement\n";
        print "L: $modifiedline\n";
}
Alan Curry
  • 14,255
  • 3
  • 32
  • 33
0

There's no xxletterxx to replace with b or c because you already replaced it with a.

hobbs
  • 223,387
  • 19
  • 210
  • 288
0

As hobbs, said, the problem is once you have replaced xxletterxx with a, the pattern no longer matches and thus the replacement doesn't occur. I'm not sure what your eventual goal is, but this works

#!/usr/bin/env perl

use warnings;
use strict;

my @replacements=("a", "b", "c");

foreach my $replacement (@replacements) {
  my $line = "xxletterxx";
  $line=~s/xxletterxx/$replacement/g;
  print "R: $replacement\n";
  print "L: $line\n";
}

as does this

#!/usr/bin/env perl

use warnings;
use strict;

my @replacements=("a", "b", "c");
my @lines = ("xxletterxx") x 3;

foreach my $replacement (@replacements) {
  my $line = shift @lines;
  $line=~s/xxletterxx/$replacement/g;
  print "R: $replacement\n";
  print "L: $line\n";
}

plus I've added strict and warnings for you :-)

Edit: a possibly more interesting example would be a modification of the last, which for each replacement attempts to change each element of @lines in turn, and stops once a replacement succeeds.

#!/usr/bin/env perl

use warnings;
use strict;

my @replacements=("a", "b", "c");
my @lines = ("xxletterxx") x 3;

foreach my $replacement (@replacements) {
  foreach my $line (@lines) {
    last if $line=~s/xxletterxx/$replacement/g;
  }

  print "R: $replacement\n";
  print "A: @lines\n";
}
Joel Berger
  • 20,180
  • 5
  • 49
  • 104
  • Hmm. The eventual goal is to run through a list of value sets, spitting out a form for each set with the values replacing fields in a template. Specifically, I have a small snippet of a Nagios config and a table of host attributes. For each row in the table I want to spit out a host definition with those attributes filled in. I have a template host definition with the fields identified by things like "xxhostgroup_namexx" and "xxhost_aliasxx". – chewtoy Aug 31 '12 at 14:15