Perl foreach и изменение переменной цикла

Я пишу script в Perl и задаю вопрос о конструкции Perl foreach.

Похоже, что если вы меняете одну из переменных цикла, она изменяется в фактическом массиве. Это действительно так, или я сделал что-то полностью неправильно?

Я хочу изменить строку как abc.abc#a на abc_abc_a (подчеркивание для не-буквенно-цифровых символов), но мне нужно сохранить исходное значение в массиве для последующего использования.

У меня есть код, который выглядит примерно так:

@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.
}

Я мог бы решить проблему, выполнив следующие действия:

@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...
}

но есть ли более эффективный способ выполнить это?

Большое спасибо!

Ответы

Ответ 1

Ты не сумасшедший; это нормальное поведение. См. perldoc perlsyn в циклах Foreach:

Если какой-либо элемент LIST является lvalue, вы можете изменить его, изменив VAR внутри петля. И наоборот, если какой-либо элемент LIST НЕ является значением lvalue, любая попытка изменить это элемент не сработает. Другими словами, переменная индекса цикла "foreach" является неявной псевдоним для каждого элемента в списке, который вы зацикливаете.

Другие итераторы цикла, такие как map, имеют схожее поведение:

map BLOCK LIST  
map EXPR,LIST

...
Обратите внимание, что $_ является псевдонимом для значения списка, поэтому его можно использовать для изменения элементы СПИСОК. Хотя это полезно и поддерживается, оно может вызывают причудливые результаты, если элементы СПИСОК не являются переменные. Использование обычной петли "foreach" для этой цели было бы  яснее в большинстве случаев. См. Также "grep" для массива, состоящего из этих элементов исходного списка, для которого BLOCK или EXPR оценивается как true.

Вы можете переписать свой код таким образом, что, по крайней мере, избавит вас от добавления дополнительной строки:

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";
}

Ответ 2

Вы всегда можете сделать копию массива перед изменением в нем элементов ('my', добавленных в appase strict), а именно:

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);

который возвращает:

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

@temp не имеет области вне цикла foreach.

Ответ 3

Вы правы. Как указано Programming Perl, переменная цикла является псевдонимом для текущего элемента массива, и вы должны сэкономить переменную цикла, если какие-либо изменения не будут влияют на исходное значение.

Ответ 4

my @strings = ('abc.abc#a', 'def.g.h#i');

print join( "\n", ( map { $_ =~ s/[^0-9A-Za-z]/_/gr } @strings ) )."\n";

r работает только в Perl 5.14 и выше.