Perl递归代码扫描目录树



在这个脚本中,递归地扫描目录,我想知道当"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('.');

这是你的代码吗?

在子例程中,您调用my @names = readdir,它定义了一个新的词法作用域变量,因此每次递归都将创建该变量的新实例。如果你用our代替my,它可能会起作用。用our定义的变量是打包作用域,这意味着每个调用将使用相同的@names变量。其实那时也没有。您正在使用readdir清除变量的前一个值。

你最好使用File::Find。File::Find与大多数Perl安装一起提供,所以它总是可用的。

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压入数组。

然后,每次弹出一个数组项。如果找到一个目录,读入它(还是反向读入)并将其压入数组。注意...

这是一段奇怪的代码,特别是如果它被发表在一本书中。

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

而且,您所指的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('.');

最新更新