用于扫描目录树的Perl递归代码

时间:2014-08-06 23:19:35

标签: perl recursion directory

在这个递归扫描目录的脚本中,我想知道调用“ScanDirectory($ name)”时会发生什么 - > “下一个”会立即执行吗?

因为如果@names在每个循环之后填充了新目录,那么我们进入@names中的第一个目录,如果有其他目录,则再次调用Scandirectory但是之前的@names中的其他目录被替换并且所以它们不被循环处理?对不起,如果我没有意义。

我知道已经有一个模块用于此目的,但我想提高我对这个循环代码如何工作的理解,这样我就可以在其他情况下处理递归代码

sub ScanDirectory {

  my $workdir = shift;
  my $startdir = cwd;

  chdir $workdir or die;
  opendir my $DIR, '.' or die;
  my @names = readdir $DIR or die;
  closedir $DIR;

  foreach my $name (@names) {
    next if ($name eq ".");
    next if ($name eq "..");

    if (-d $name) {
      ScanDirectory($name);
      next;
    }

  }
  chdir $startdir or die;
}

ScanDirectory('.');

2 个答案:

答案 0 :(得分:0)

这是你的代码吗?

在子例程中,调用my @names = readdir定义一个新的词法范围变量,因此每次递归都将创建该变量的新实例。如果您使用our而不是my,它可能会有效。使用our定义的变量是打包范围,这意味着每个调用将使用相同的@names变量。实际上即便如此。您已使用readdir清除变量的先前值。

您最好使用File::Find。大多数Perl安装都附带File::Find,因此它始终可用。

use strict;
use warnings; 

use File::Find;

my @names;
find ( sub {
          next if $_ eq "." or $_ eq "..";
          push @names, $File::Find::name;
     }, "."
);

这更容易理解,更容易编写,更灵活,更高效,因为它不会递归调用自身。大多数情况下,如果没有sub嵌入到函数中,您就会看到这一点:

my @names;
find ( \&wanted, ".");

sub wanted {
    next if $_ eq "." or $_ eq "..";
    push @names, $File::Find::name;
}

如果子程序相当小,我更喜欢嵌入子程序。它可以防止子程序偏离find调用,并且可以防止在子程序中使用@names的神秘实例而没有明确的定义。

好的,他们都是一样的。两者都是子程序引用(一个称为wanted,一个是匿名子程序)。但是,@names的首次使用并不显得如此神秘,因为它在find调用正上方的字面上定义。

如果你必须从头编写自己的例程(也许是家庭作业?),那么就不要使用递归。使用push颠倒 readdir推送到数组中。

然后,一次弹出一个数组的项目。如果找到目录,请将其读取(再反过来)并将其推入阵列。请注意...

答案 1 :(得分:0)

这是编写奇怪的代码,特别是如果它出版在书中。

您的困惑是因为@names数组被声明为词法,这意味着它仅存在于当前块的范围内,并且对于特定的堆栈帧是唯一的(子例程调用) 。因此,scan_directory的每个调用(本地标识符实际上不应包含大写字母)都有自己的独立@names数组,当子例程退出时它会消失,并且不存在“替换”的问题< / em>内容。

此外,您所指的next是多余的:它会跳到@names数组的下一次迭代,这就是没有它的情况。

这样写的好多了

sub scan_directory {
  my ($workdir) = @_;

  my $startdir = cwd;
  chdir $workdir or die $!;

  opendir my $dh, '.' or die $!;

  while (my $name = readdir $dh) {
    next if $name eq '.' or $name eq '..';
    scan_directory($name) if -d $name;
  }

  chdir $startdir or die $!;
}

scan_directory('.');