为何Perl仅在使用<<>>时未呈现深层拷贝的预期效果?
Perl数组
@argv被清空的原因与解决办法 问题描述
我将@ARGV的值存入@argv数组,随后通过foreach遍历@argv。循环内把@argv的每个元素赋值给@ARGV,用<<>>(钻石操作符)逐个处理文件。循环结束后打算再次遍历@argv,将处理后的文件重命名为隐藏文件,却发现最终foreach循环前@argv已为空。测试shift或delete的类似场景时,原数组未受影响,就像做了深拷贝一样,不清楚为何<<>>会影响原数组,而shift/delete不会。
问题代码
my %argv_parsed = &parse_arguments(); my @argv = @ARGV; # ... $^I = $backup_enabled ? '.bak' : ''; foreach (@argv) { my $file_name = $_; my $is_in_code = 0; # ... @ARGV = $file_name; while (<<>>) { $is_in_code = ! $is_in_code if /^`\``/; if ($is_in_code || ! /^( {0,3})(#{1,6})(.+)/) { print; next; } my ($pre_space, $hash, $title) = ($1, $2, $3); $title =~ s/^ +(\d*\.){1,6}// if $is_marked; # ... say "$pre_space$hash " . (join '.', @head_number_interim) . ".$title"; } unless ($is_marked) { open FILE, '>>', $file_name; say FILE $mark_comment; close FILE; } } exit 0 unless $backup_enabled; foreach (map { "$_$^I" } @argv) { my ($name, $path) = fileparse $_; rename "$path$name", "$path.$name"; }
核心原因
问题出在Perl的foreach迭代机制和全局默认变量$_的冲突上:
foreach (@argv)遍历数组时,循环变量$_是@argv对应元素的别名,而非独立拷贝。修改$_就等于直接修改@argv里的元素。<<>>(钻石操作符)在while循环中,会默认将读取的每一行内容赋值给全局$_。这就导致内层循环的$_直接覆盖了外层foreach循环中@argv元素的别名,把@argv里的文件名逐个替换成了文件内容,最终文件读完后$_被设为undef,@argv的元素也变成undef,看起来就像空数组。
而shift/delete的场景没有触发这个问题,是因为这些操作要么修改的是其他数组(比如@ARGV),要么没有覆盖foreach的迭代变量$_,不会影响@argv的元素。
解决办法
有两种简单可靠的修复方式:
方式一:显式声明foreach循环变量
将foreach (@argv)改为带独立变量的遍历,避免依赖全局$_。这样内层循环的$_不会影响@argv的元素:
foreach my $file_name (@argv) { my $is_in_code = 0; # ... 移除原有的my $file_name = $_; @ARGV = $file_name; while (<<>>) { # 这里的$_是当前行内容,和外层循环变量无关 $is_in_code = ! $is_in_code if /^`\``/; # ... 其余代码保持不变 } # ... 其余代码保持不变 }
方式二:显式指定while循环的行变量
不使用默认的$_,把每行内容读入独立变量,彻底隔离内外层的变量冲突:
foreach (@argv) { my $file_name = $_; my $is_in_code = 0; # ... @ARGV = $file_name; while (my $line = <<>>) { $is_in_code = ! $is_in_code if $line =~ /^`\``/; if ($is_in_code || $line !~ /^( {0,3})(#{1,6})(.+)/) { print $line; next; } my ($pre_space, $hash, $title) = ($1, $2, $3); $title =~ s/^ +(\d*\.){1,6}// if $is_marked; # ... 其余代码需将所有用到$_的地方替换为$line say "$pre_space$hash " . (join '.', @head_number_interim) . ".$title"; } # ... 其余代码保持不变 }
内容的提问来源于stack exchange,提问作者csh
相关产品推荐
相关产品推荐

