5

我最近开始学习 Perl,我最近的一个任务是在一堆文件中搜索一个特定的字符串。用户提供目录名称作为参数,程序在该目录中的所有文件中搜索模式。使用readdir()我已经设法构建了一个包含所有可搜索文件名的数组,现在需要搜索每个文件的模式,我的实现看起来像这样 -

sub searchDir($) {
    my $dirN = shift;
    my @dirList = glob("$dirN/*");
    for(@dirList) {
        push @fileList, $_ if -f $_;

    }
    @ARGV = @fileList;
    while(<>) {
        ## Search for pattern
    }
}

我的问题是 - 是否可以像上面那样手动加载 @ARGV 数组并使用 <> 运算符扫描单独的行,还是应该单独打开/扫描/关闭每个文件?如果这个处理存在于子程序中而不是主函数中,会有什么不同吗?

4

5 回答 5

9

关于操纵@ARGV 的话题——这绝对是有效的代码,Perl 肯定允许你这样做。不过,我认为这不是一个好的编码习惯。我看到的大多数使用“while (<>)”习语的代码都是使用它从标准输入中读取,这就是我最初希望您的代码执行的操作。更易读的模式可能是单独打开/关闭每个输入文件:

foreach my $file (@files) {
    open FILE, "<$file" or die "Error opening file $file ($!)";
    my @lines = <FILE>;
    close FILE or die $!;

    foreach my $line (@file) {
        if ( $line =~ /$pattern/ ) {
            # do something here!
        }
    }
}

这对我来说会更容易阅读,尽管它是几行代码。Perl 为您提供了很大的灵活性,但我认为这使得在 Perl 中开发自己的风格变得更加重要,这种风格对您(以及您的同事,如果这对您的代码/职业很重要)来说是可读和可理解的。

将子例程放在主函数或子例程中也主要是您应该尝试和思考的风格决定。现代计算机在这方面的速度如此之快,以至于样式和可读性对于这样的脚本更为重要,因为您不太可能遇到这样的脚本使您的硬件负担过重的情况。

祝你好运!Perl 很有趣。:)

编辑:当然,如果他有一个非常大的文件,他应该做一些比将整个文件吞入数组更聪明的事情。在这种情况下,这样的事情肯定会更好:

while ( my $line = <FILE> ) {
    if ( $line =~ /$pattern/ ) {
        # do something here!
    }
}

当我写“您不太可能遇到这样的脚本使您的硬件负担过重的情况”时,这一点旨在涵盖这一点,抱歉没有更具体。再说了,谁还有 4GB 的硬盘,更别说 4GB 的文件了?:P

另一个编辑:根据评论者的建议仔细阅读互联网后,我意识到有比 4GB 大得多的硬盘可供购买。我感谢评论者指出这一点,并承诺在未来永远不会尝试在互联网上写讽刺评论。

于 2009-02-03T05:25:39.190 回答
3

我更喜欢这个更明确和可读的版本:

#!/usr/bin/perl -w 

foreach my $file (<$ARGV[0]/*>){
    open(F, $file) or die "$!: $file";
    while(<F>){
      # search for pattern
    }
    close F;
}

但也可以操纵@ARGV

#!/usr/bin/perl -w 

@ARGV = <$ARGV[0]/*>;
while(<>){
    # search for pattern
}
于 2009-02-03T04:37:49.207 回答
1

while (<>)是的,在开始' '循环之前调整参数列表是可以的;在循环内调整它几乎是鲁莽的。例如,如果您处理选项参数,您通常会从 @ARGV 中删除项目;在这里,您正在添加项目,但它仍然会更改 @ARGV 的原始值。

代码是在子例程中还是在“主函数”中,这并不奇怪。

于 2009-02-03T04:36:21.177 回答
1

前面的答案很好地涵盖了您的主要 Perl 编程问题。

因此,让我评论一下基本问题:如何在一堆文件中找到模式。

根据操作系统,调用专门的外部程序可能是有意义的,比如

grep -l <pattern> <path>

在 Unix 上。

根据您需要对包含该模式的文件执行的操作以及命中/未命中率的大小,这可能会节省大量时间(并重复使用经过验证的代码)。

于 2009-02-03T10:24:20.803 回答
0

调整@ARGV 的最大问题是它是一个全局变量。另外,你应该知道它while (<>)具有特殊的魔法属性。(读取每个文件@ARGV或处理STDIN是否@ARGV为空,测试定义性而不是真实性)。为了减少需要理解的魔法,我会避免它,除了快速黑客工作。

您可以通过检查来获取当前文件的文件名$ARGV

您可能没有意识到,但实际上您正在影响两个全局变量,而不仅仅是@ARGV. 你也打$_。本地化也是一个非常非常好的主意$_

local您可以通过使用本地化更改 来减少修改全局变量的影响。

顺便说一句,还有另一个重要的、微妙的魔法<>。假设您要返回文件中匹配项的行号。你可能会想,好吧,检查 perlvar 并 find$.给出最后访问的句柄中的行号——太棒了。但是这里潜伏一个问题——文件$.之间没有重置。@ARGV如果您想知道总共处理了多少行,但如果您想知道当前文件的行号,这很好。幸运的是,有一个简单的技巧eof可以解决这个问题。

use strict;
use warnings;

...

searchDir( 'foo' );

sub searchDir {
    my $dirN    = shift;
    my $pattern = shift;

    local $_;

    my @fileList = grep { -f $_ } glob("$dirN/*");

    return unless @fileList;  # Don't want to process STDIN.

    local @ARGV;

    @ARGV = @fileList;
    while(<>) {
        my $found = 0;
        ## Search for pattern
        if ( $found ) {
            print "Match at $. in $ARGV\n";
        }
    }
    continue {
        # reset line numbering after each file.
        close ARGV  if eof;  # don't use eof().
    }
}

警告:我刚刚在浏览器中修改了您的代码。我没有运行它,可能有错别字,如果不进行一些调整可能无法工作

更新:使用local而不是的原因my是它们做的事情非常不同。 my创建一个新的词法变量,该变量仅在包含的块中可见,不能通过符号表访问。 local保存现有的包变量并将其别名为新变量。新的本地化版本在任何后续代码中都可见,直到我们离开封闭块。请参阅perlsub:通过 local() 的临时值

在制作新变量并使用它们的一般情况下,这my是正确的选择。 local当您使用全局变量时是合适的,但您要确保不会将您的更改传播到程序的其余部分。

这个简短的脚本演示了本地:

$foo = 'foo';

print_foo();
print_bar();
print_foo();

sub print_bar {
    local $foo;
    $foo = 'bar';
    print_foo();
}

sub print_foo {
    print "Foo: $foo\n";
}
于 2009-02-04T05:18:12.480 回答