foreach和特殊变量$ _的行为不符合预期

时间:2013-07-04 06:35:19

标签: perl

我正在学习Perl并编写了一个小脚本来打开perl文件并删除注释

# Will remove this comment

my $name = ""; # Will not remove this comment

#!/usr/bin/perl -w< - 不会删除此特别评论

要编辑的文件名称作为参数通过终端

传递
die "You need to a give atleast one file-name as an arguement\n" unless (@ARGV);

foreach (@ARGV) {
    $^I = "";
    (-w && open FILE, $_) || die "Oops: $!";
    /^\s*#[^!]/ || print while(<>);
    close FILE;
    print "Done! Please see file: $_\n";
}

现在我通过终端运行它: perl removeComments file1.pl file2.pl file3.pl

我得到了输出: Done! Please see file:

这个脚本正如我所期待的那样正常工作

问题1:为什么$_没有打印文件名?

问题2:由于循环运行3次,为什么Done! Please see file:只打印一次?

如何在尽可能少的行中编写此脚本?

如果你有时间,也请评论我的代码。

谢谢。

4 个答案:

答案 0 :(得分:9)

while存储由菱形运算符&lt;&gt;读取的行。进入$ _,所以你要写的是存储文件名的变量。

另一方面,您使用open打开文件,但实际上并未使用句柄来阅读;它使用空钻石运算符代替。空菱形运算符对@ARGV中的文件进行隐式循环,删除文件名,因此foreach只运行一次。

要解决第二个问题,您可以使用while(<FILE>),或者重写循环以利用<>中的隐式循环并将整个程序编写为:

$^I = "";
/^\s*#[^!]/ || print while(<>);

答案 1 :(得分:3)

这是一种更具可读性的方法。

#!/usr/bin/perl

# always!!
use warnings;
use strict;

use autodie;
use File::Copy;

# die with some usage message
die "usage: $0 [ files ]\n" if @ARGV < 1;


for my $filename (@ARGV) {
    # create tmp file name that we are going to write to
    my $new_filename = "$filename\.new";

    # open $filename for reading and $new_filename for writing
    open my $fh, "<", $filename;
    open my $new_fh, ">", $new_filename;

    # Iterate over each line in the original file: $filename,
    # if our regex matches, we bail out. Otherwise we print the line to
    # our temporary file.  
    while(my $line = <$fh>) {
       next if $line =~ /^\s*#[^!]/; 
       print $new_fh $line;
    }

    close $fh;
    close $new_fh;

    # use File::Copy's move function to rename our files. 
    move($filename, "$filename\.bak");
    move($new_filename, $filename);

    print "Done! Please see file: $filename\n";
}

示例输出:

$ ./test.pl a.pl b.pl 
Done! Please see file: a.pl
Done! Please see file: b.pl

$ cat a.pl
#!/usr/bin/perl

print "I don't do much\n"; # comments dont' belong here anyways

exit;

print "errrrrr";

$ cat a.pl.bak
#!/usr/bin/perl

# this doesn't do much
print "I don't do much\n"; # comments dont' belong here anyways

exit;

print "errrrrr";

答案 2 :(得分:2)

使用多个循环并尝试获得正确的$_是不安全的。 while循环正在查看您的$_。尝试在该循环中为您的文件指定特定名称。你可以这样做:

foreach my $filename(@ARGV) {
    $^I = "";
    (-w && open my $FILE,'<', $filename) || die "Oops: $!";
    /^\s*#[^!]/ || print while(<$FILE>);
    close FILE;
    print "Done! Please see file: $filename\n";
}

或那样:

foreach (@ARGV) {
    my $filename = $_;
    $^I = "";
    (-w && open my $FILE,'<', $filename) || die "Oops: $!";
    /^\s*#[^!]/ || print while(<$FILE>);
    close FILE;
    print "Done! Please see file: $filename\n";
}

请不要将裸字用于文件句柄,并使用3参数open

  • open my $FILE, '<', $filename - 好

  • open FILE $filename - 糟糕

答案 3 :(得分:0)

更简单的解决方案:不要使用$_

首次编写Perl时,它被认为是Awk和shell的替代品,而Perl则大量借用了这种语法。 Perl 的可读性创建了特殊变量$_,它允许您使用各种命令而无需创建变量:

while ( <INPUT> ) {
   next if /foo/;
   print OUTPUT;
}

问题是如果一切都在使用$_,那么一切都会在许多不愉快的副作用中产生$_

现在,Perl是一种更复杂的语言,并且具有本地作用域变量之类的东西(提示:你不使用local来创建这些变量 - 这只是给出了_package变量(又名全局变量)是一个本地值。)

由于您正在学习Perl,因此您可能正确学习Perl 。问题是有太多的书仍然基于Perl 3.x.找一本包含现代实践的书或web page

在您的程序中,$_从文件名切换到文件中的行,然后返回到下一个文件。这让你感到困惑。如果使用了命名变量,则可以区分文件和行。

我使用更现代的语法重写了你的程序,但你的逻辑是相同的:

use strict;
use warnings;
use autodie;
use feature qw(say);

if ( not $ARGV[0] ) {
    die "You need to give at least one file name as an argument\n";
}

for my $file ( @ARGV ) {
    # Remove suffix and copy file over
    if ( $file =~ /\..+?$/ ) {
       die qq(File "$file" doesn't have a suffix);
    }
    my ( $output_file = $file ) =~ s/\..+?$/./;  #Remove suffix for output
    open my $input_fh, "<", $file;
    open my $output_fh, ">", $output_file;
    while ( my $line = <$input_fh> ) {
       print {$output_fh} $line unless /^\s*#[^!]/;
    }
    close $input_fh;
    close $output_fh;
}

这比你的程序版本更加打字,但是更容易看到正在发生的事情和维护。