【问题标题】:Perl: How to stop File::Find entering directory recursively?Perl:如何以递归方式停止 File::Find 进入目录?
【发布时间】:2016-05-01 17:03:47
【问题描述】:

我正在查看 Perl 的 File::Find 模块并尝试了以下方式:

#!/usr/bin/perl

use warnings;
use strict;

use File::Find;

find({wanted => \&listfiles,
        no_chdir => 1}, ".");


sub listfiles{
    print $File::Find::name,"\n";
}

现在当我运行它时,我得到以下输出:

Noob@Noob:~/tmp$ perl test.pl 
.
./test.txt
./test.pl
./test1.txt
./hello
./hello/temp.txt

现在,我在想,通过设置no_chdir=>1,我会让我的代码在遇到任何目录时不进入任何目录。但输出清楚地表明我的代码正在进入hello 目录并列出其文件。

那么,如何将我的代码更改为像ls 一样的行为而不进入任何目录。另外,我的文件/目录名称前面有./,可以删除吗?

我正在使用 Perl 5.14。

【问题讨论】:

  • 确保 listfiles 不会为目录返回 true。手册页表明 no_chdir 需要您想要的功能才能意识到这一点。
  • 你的最终目标是什么?只是列出给定目录中的文件? File::Find 模块遍历目录树。尽管可以让您想要的函数跳过子目录及其内容,但您最终会做的工作比仅列出单个目录中的文件要多。
  • no_chdir 不会阻止遍历,它只会阻止实际的chdir 系统调用。
  • 如果你不想递归,你还不如直接globopendir来读取文件。
  • @JonahBishop:我的最终目标是编写一个执行ls 功能的程序,但我最终写了print"$_ \n" for glob("*");

标签: perl


【解决方案1】:

$File::Find::prune 可用于避免递归到目录。

use File::Find qw( find );

my $root = '.';
find({
   wanted   => sub { listfiles($root); },
   no_chdir => 1,
}, $root);

sub listfiles {
   my ($root) = @_;
   print "$File::Find::name\n";
   $File::Find::prune = 1  # Don't recurse.
      if $File::Find::name ne $root;
}

如果您愿意,可以有条件地设置prune

use File::Basename qw( basename );
use File::Find     qw( find );

my %skip = map { $_ => 1 } qw( .git .svn ... );

find({
   wanted   => \&listfiles,
   no_chdir => 1,
}, '.');

sub listfiles {
   if ($skip{basename($File::Find::name)}) {
      $File::Find::prune = 1;
      return;
   }

   print "$File::Find::name\n";
}

no_chdir 不是必需的——它与你想要做的事情无关——但我喜欢它的作用(防止更改 cwd),所以我把它留在了。

【讨论】:

  • 谢谢。您能否澄清prevents changes to the cwd 的确切含义。
  • 当前工作目录(cwd)是用于将相对路径转换为绝对路径的目录。这就是chdir 的变化。默认情况下,find 会 chdir 进入当前文件所在的目录。
  • @ikegami;不幸的是,更多的是必要的,因为要报告的第一个节点是要搜索的节点。修剪这将导致仅包含该目录的列表
  • @ikegami:你的代码对我不起作用,它只是打印.,然后它会自行退出。
  • @Noob:那是因为我提到的问题。除了在find 调用中搜索的目录之外,您必须修剪所有目录
【解决方案2】:

虽然我认为 TLP 建议使用globopendir 是最适合您的情况,但另一种选择是使用File::Find::Rule--Find::File 的接口--与maxdepth(1) 停止目录递归:

use Modern::Perl;
use File::Find::Rule;

my $directory = '.';
my @files = File::Find::Rule->maxdepth( 1 )
                            ->file
                            ->name( '*.txt' )
                            ->in( $directory );
say for @files;

在这种情况下,只有*.txt 文件名将被传递给@files

样本输出:

A.txt
B.txt
columns.txt
data.txt

【讨论】:

    【解决方案3】:

    最简单的方法是使用preprocess 参数从每个正在处理的目录中删除所有目录。这意味着它永远不会低于指定要搜索的目录

    传递给preprocess 子例程的参数列表是当前目录中的节点-readdir 的输出。返回的值是相同的列表,但根据您希望它们的处理方式进行排序和过滤。此代码只是删除所有目录

    删除初始./ 的最佳方法是使用File::Spec 中的rel2abs。请注意,启用no_chdir 将破坏代码,因为默认情况下rel2abs 将当前工作目录作为基本目录。使用no_chdir 意味着显式传递基本目录参数

    use strict;
    use warnings;
    
    use File::Find 'find';
    use File::Spec;
    
    find({ wanted => \&listfiles, preprocess => \&nodirs }, '.');
    
    sub nodirs {
      grep ! -d, @_;
    }
    
    sub listfiles {
      my $filename = File::Spec->abs2rel($File::Find::name);
      print $filename, "\n";
    }
    

    【讨论】:

    • 很好,我喜欢预处理参数,但我不想删除我的目录我只想让我的输出看起来像 ls command 并且我已经知道我可以写 print"$_ \n" for glob("*"); 来实现但我正在考虑使用File 模块及其功能实现此目的的方法。
    • 我不确定你的意思。你说你想从文件名的前面删除. /,这段代码就是这样做的。它显示文件的基本名称,如ls 确实
    • @Borodin:我认为他的意思是 ls 显示文件夹的所有files and directory 而不递归地进入任何目录,除非-R flag 指定。您的代码所做的是它可以正常显示所有文件,但您已删除所有目录。
    【解决方案4】:

    使用 preprocess 和一个 nop 表示 “wanted”

    use File::Find qw( find );
    
    my @f;
    
    find(
    { 
        wanted => sub {},
        preprocess => sub {
            push(@f, grep !/^\.?\./,@_); # @_ is equivalent to readdir(DIR)
                                         # '.' and '..' come for free
            # find uses what you return here for processing. 
            # since nothing (an empty list) is returned, it has nothing to recurse on
         }
    },
    @dirs);
    

    如果你想要完整的路径,然后像这样映射 grep

    push(@f, map { Spec->catfile($File::Find::dir,$_); } grep !/^\.?\./,@_);
    

    或者,只是

     push(@f, map { $File::Find::dir . '/' . $_); } grep !/^\.?\./,@_);
    

    对于后者,您可能会在 Windows 上混合使用 \ 和 /,这取决于您如何指定 @dirs 的元素

    【讨论】:

      猜你喜欢
      • 2019-12-11
      • 1970-01-01
      • 2011-02-17
      • 2013-07-29
      • 2010-09-06
      • 1970-01-01
      • 1970-01-01
      • 2013-05-18
      相关资源
      最近更新 更多