tags:

views:

100

answers:

3

I am writing a script in Perl and have a question about Perl's foreach construct.

It appears that if you change one of the loop variables it changes in the actual array. Is this in fact the case, or have I done something completely wrong?

I want to change a string like abc.abc#a to abc_abc_a (underscores for non alpha-numeric characters), but I need to preserve the original value in the array for later use.

I have code that looks something like this:

@strings = ('abc.abc#a', 'def.g.h#i');
foreach my $str (@strings){
    $str =~ s/[^0-9A-Za-z]/_/g;
    print $str, "\n"; #Actually I use the string to manipulate files.
}

I could solve the problem by doing the following:

@strings = ('abc.abc#a', 'def.g.h#i');
foreach my $str (@strings){
    my $temp = $str; #copy to a temporary value
    $temp =~ s/[^0-9A-Za-z]/_/g;
    print $temp, "\n"; #$str remains untouched...
}

but is there a more efficient way to accomplish this?

Thank you very much!

+2  A: 

You are correct. As Programming Perl states, the loop variable is an alias for the current array element and you must save off the loop variable if any modifications are not to affect the original value.

David Harris
+8  A: 

You're not crazy; this is normal behaviour. See perldoc perlsyn under Foreach loops:

If any element of LIST is an lvalue, you can modify it by modifying VAR inside the loop. Conversely, if any element of LIST is NOT an lvalue, any attempt to modify that element will fail. In other words, the "foreach" loop index variable is an implicit alias for each item in the list that you're looping over.

Other loop iterators such as map have similar behaviour:

map BLOCK LIST  
map EXPR,LIST

...
Note that $_ is an alias to the list value, so it can be used to modify the elements of the LIST. While this is useful and supported, it can cause bizarre results if the elements of LIST are not variables. Using a regular "foreach" loop for this purpose would be clearer in most cases. See also "grep" for an array composed of those items of the original list for which the BLOCK or EXPR evaluates to true.

You could rewrite your code this way, which would at least save you from adding an extra line:

my @strings = ('abc.abc#a', 'def.g.h#i');
foreach my $str (@strings){
    (my $copy = $str) =~ s/[^0-9A-Za-z]/_/g;
    print $copy, "\n";
}
Ether
+3  A: 

You can always make a copy of the array before modifying elements in it ('my' added to appease strict), viz.:

my @strings = ('abc.abc#a', 'def.g.h#i');
foreach my $str (my @temp = @strings) {
    $str =~ s/[^0-9A-Za-z]/_/g;
    print "$str\n"; #Actually I use the string to manipulate files.
}
use Data::Dumper;
print Dumper(\@strings);

which returns:

abc_abc_a
def_g_h_i
$VAR1 = [
          'abc.abc#a',
          'def.g.h#i'
        ];

@temp doesn't have scope outside of the foreach loop.

MkV