【问题标题】:directory tree warning目录树警告
【发布时间】:2023-04-04 16:23:01
【问题描述】:

我写了一些脚本,递归打印目录的内容。但它会为每个文件夹打印警告。如何解决这个问题?

示例文件夹:

dev# cd /tmp/test
dev# ls -p -R
test2/
testfile
testfile2

./test2:
testfile3
testfile4

我的代码:

#!/usr/bin/perl

use strict;
use warnings;

browseDir('/tmp/test');

sub browseDir {
    my $path = shift;
    opendir(my $dir, $path);
    while (readdir($dir)) {
        next if /^\.{1,2}$/;
        if (-d "$path/$_") {
            browseDir("$path/$_");
        }
        print "$path/$_\n";
    }
    closedir($dir);
}

和输出:

dev# perl /tmp/cotest.pl
/tmp/test/test2/testfile3
/tmp/test/test2/testfile4
使用未初始化的值 $_ in /tmp/cotest.pl 第 16 行的连接 (.) 或字符串。
/tmp/test/
/tmp/test/testfile
/tmp/test/testfile2

【问题讨论】:

  • File::Spec no_upwards() 函数来隐藏所有目录,如...。示例:@paths = File::Spec->no_upwards( @paths );
  • 您应该在程序中添加use 5.012;,因为您使用while(readdir($dir)){...},这在早期版本中不起作用。

标签: perl


【解决方案1】:

你可以试试那个代码:

    #!/usr/bin/perl

    use strict;
    use warnings;

    browseDir('/tmp');

    sub browseDir {
        my $path = shift;
        opendir(my $dir, $path);
        while (readdir($dir)) {
            next if /^\.{1,2}$/;
            print "$path/$_\n";
            if (-d "$path/$_") {
                browseDir("$path/$_");
            }
        }
        closedir($dir);
    }

如果你得到那个错误,那是因为你在使用变量 $_ 之前调用了 browseDir()。

【讨论】:

  • 谢谢,它有效。另外,如果我不使用 $_ 变量,则不会打印警告。 while (my $s = readdir($dir)) {...}
  • OP 是否更改了他的代码?我找不到你和他的区别。
  • print "$path/$_\n"; 向上移动
  • @Suic 它适用于另一个变量。但重点是,$_ 在函数调用后被重置。
  • 问题是循环while (readdir($dir)) {...} 仅在$_ 未定义时终止。因为$_ 是一个全局变量,所以在打印其值之前调用browseDir 将尝试打印undef。您可以通过对循环使用词法变量或在子例程顶部使用 local $_ 来解决此问题。
【解决方案2】:

为什么不使用File::Find 模块?自 Perl 5.x 以来,它几乎包含在所有 Perl 发行版中。它不是我最喜欢的模块,因为它的工作方式有点混乱,但它做得很好。

您定义了一个wanted 子例程,它执行您想要的操作并过滤掉您不想要的操作。在这种情况下,您几乎打印了所有内容,因此 wanted 所做的只是打印找到的内容。

File::Find 中,文件名保存在$File::Find::name 中,该文件的目录在$File::Find::dir 中。 $_ 是文件本身,可用于测试。

这是您想要的基本方式:

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

use File::Find;

my $directory = `/tmp/test`;

find ( \&wanted, $directory );

sub wanted {
    say $File::Find::Name;
}

我更喜欢将我的wanted 函数放在我的find 子例程中,这样它们就在一起了。这相当于上面的:

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

use File::Find;

my $directory = `/tmp/test`;

find ( 
    sub { 
        say $File::Find::Name
    },
    $directory,
);

良好的编程要求不要在子程序中打印。相反,您应该使用子例程来存储和返回您的数据。不幸的是,find 根本没有返回任何东西。您必须使用 global 数组来捕获文件列表,然后将它们打印出来:

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

use File::Find;

my $directory = `/tmp/test`;
my @directory_list;

find ( 
    sub { 
        push @directory_list, $File::Find::Name
    }, $directory );

for my $file (@directory_list) {
   say $file;
}

或者,如果您更喜欢单独的 wanted 子例程:

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

use File::Find;

my $directory = `/tmp/test`;
my @directory_list;

find ( \&wanted, $directory );
sub wanted {
    push @directory_list, $File::Find::Name;
}

for my $file (@directory_list) {
   say $file;
}

我想要的子例程依赖于一个非本地子例程的数组这一事实困扰着我,这就是为什么我更喜欢将wanted 子例程嵌入到我的find 调用中。

您可以做的一件事是使用您的子例程过滤掉您想要的内容。假设您只对 JPG 文件感兴趣:

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

use File::Find;

my $directory = `/tmp/test`;
my @directory_list;

find ( \&wanted, $directory );
sub wanted {
    next unless /\.jpg$/i;  #Skip everything that doesn't have .jpg suffix
    push @directory_list, $File::Find::Name;
}

for my $file (@directory_list) {
   say $file;
}

在我将它推入我的@directory_list 数组之前,请注意所需的子例程如何对我不想要的任何文件执行next。同样,我更喜欢嵌入:

find (sub {
    next unless /\.jpg$/i;  #Skip everything that doesn't have .jpg suffix
    push @directory_list, $File::Find::Name;
}

我知道这不是你问的,但我只是想让你知道 Find::File 模块并向你介绍 Perl 模块(如果你还不知道的话),它可以增加很多Perl 的功能。

【讨论】:

    【解决方案3】:

    您在调用browseDir 之前在$_ 中放置了一个值,并且您希望它在调用browseDir 之后出现(一个合理的预期),但browseDir 修改了该变量。

    只需将local $_; 添加到browseDir 以确保在子退出之前撤消对它的任何更改。


    与您的问题无关,以下是其他三个问题:

    • 甚至没有最小的错误检查!
    • 您可能会用尽目录句柄来导航深层目录。
    • 您过滤掉了文件".\n""..\n"

    修复:

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    browseDir('/tmp/test');
    
    sub browseDir {
        my $path = shift;
    
        opendir(my $dh, $path) or die $!;
        my @files = readdir($dh);
        closedir($dh);
    
        for (@files) {
            next if /^\.{1,2}z/;
            if (-d "$path/$_") {
                browseDir("$path/$_");
            }
    
            print "$path/$_\n";
        }
    }
    

    最后,为什么不用File::Find::Rule 这样的模块呢?

    use File::Find::Rule qw( );
    print "$_\n" for File::Find::Rule->in('/tmp');
    

    注意:在 5.12 之前,while (readir($dh)) 必须写成 while (defined($_ = readdir($dh)))

    【讨论】:

    • 实际上 while(readdir($dir)){...}5.12 转换为 while(defined($_ = readdir($dir))){...}commit 不幸的是,这从未成为三角洲。
    • @Brad Gilbert,哦,真的!那么问题是缺乏$_ 的本地化。递归调用破坏了父级在$_ 中保存的值。更新了答案。
    • 是的,我在想那段代码可以修改为使用local $_。它可能从未出现,因为它通常仅用于readline。唯一向后兼容的问题是 $_ 在循环之后会被重置。
    • @Brad Gilbert,我的意思是“OP 的问题是缺乏 $_ 的本地化”。我并不是建议 while readline 应该这样做。
    • 在帖子中添加了 File::Find::Rule 解决方案,因为它只有两行。
    猜你喜欢
    • 1970-01-01
    • 2013-06-08
    • 2015-02-20
    • 2016-06-08
    • 2013-02-21
    • 2020-02-22
    • 1970-01-01
    • 1970-01-01
    • 2016-01-08
    相关资源
    最近更新 更多