【问题标题】:Should I manually set Perl's @ARGV so I can use <> to open, scan, and close files?我应该手动设置 Perl 的 @ARGV 以便我可以使用 <> 打开、扫描和关闭文件吗?
【发布时间】:2010-10-05 02:17:34
【问题描述】:

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

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

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

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

【问题讨论】:

标签: perl file input


【解决方案1】:

调整@ARGV 的最大问题是它是一个全局变量。此外,您应该知道while (&lt;&gt;)special magic attributes。 (如果@ARGV 为空,则读取@ARGV 中的每个文件或处理STDIN,测试定义性而不是真实性)。为了减少需要理解的魔法,我会避免它,除了快速黑客工作。

查看$ARGV可以获取当前文件的文件名。

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

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

顺便说一句,&lt;&gt; 还有另一个重要而微妙的魔力。假设您要返回文件中匹配项的行号。你可能会想,好吧,检查 perlvar 并找到$. 给出了最后访问的句柄中的行号——太棒了。但是这里潜伏一个问题——$.@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: Temporary Values Via 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";
}

【讨论】:

  • 为什么你会为 $_ 和 @ARGV 变量使用'local'而不是使用'my'在词法范围内。
  • 谢谢,perlsub 文档对解释在处理特殊的全局变量和标点符号时需要使用 local 很有帮助。
【解决方案2】:

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

所以让我来评论一下基本问题:如何在一堆文件中找到一个模式。

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

grep -l <pattern> <path>

在 Unix 上。

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

【讨论】:

  • 非常感谢您的建议,我实际上是在 perl 中尝试 egrep 的基本端口,以便处理我的文件 io 和正则表达式。
  • 将其保存在一个地方(例如,将 egrep 移植到 perl,因此所有 perl、可自我维护的可能便携解决方案,包含自制错误)与使用操作系统提供的工具之间始终保持平衡(快速解决方案,经过测试,优化,但可能不可移植)。视情况而定!
  • 确实如此。我会选择操作系统工具来完成工作,但在这种情况下,我试图利用我对 perl 的 file-io 知识来加强我的学习(大约一周前拿起一本 Perl 书)。
【解决方案3】:

关于操纵@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 大得多的硬盘可供购买。我感谢评论者指出这一点,并承诺在未来永远-永远尝试在互联网上写讽刺评论。

【讨论】:

  • 没有必要先将整个文件存储在内存中,然后在单独的循环中遍历它。例如,想象一下他有一个 4GB 的文件!
  • "谁还有 4GB 硬盘?" - 你在哪个年代?我的硬盘是 150GB。也许您的意思是 4GB 内存,这稍微可信一些。但是我的电脑有2GB的内存。那你到底在说什么?
【解决方案4】:

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

#!/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
}

【讨论】:

  • 致原发帖者:注意使用通配符 () 而不是 readdir()。
【解决方案5】:

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

代码是在子程序中还是在“主函数”中,这并不重要。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-18
    • 2011-07-04
    • 2014-02-26
    • 2023-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多