有什么理由在Perl中使用glob而不是readdir(反之亦然)?

这个问题是从这个问题分离出来的。 一些历史:当我第一次学习Perl时,我几乎总是使用glob而不是opendir + readdir因为我发现它更容易。 然后后来的各种post和阅读build议glob是坏的,所以现在我几乎总是使用readdir

在思考了最近的这个问题之后,我意识到,我select其中一个的原因可能是混帐。 所以,我会列举一些优点和缺点,希望有更多经验的Perl人士可以加以澄清。 简而言之,问题是有没有令人信服的理由,喜欢globreaddirreaddirglob (在一些或所有情况下)?

glob优点:

  1. 没有dotfiles(除非你要求)
  2. 项目的顺序是有保证的
  3. 不需要手动将目录名称预先添加到项目上
  4. 更好的名字(来吧 – globreaddir不是比赛,如果我们只是通过名字来判断)
  5. (来自ysth的回答;参考下面的glob cons 4)可以返回不存在的文件名:

     @deck = glob "{A,K,Q,J,10,9,8,7,6,5,4,3,2}{\x{2660},\x{2665},\x{2666},\x{2663}}"; 

glob缺点:

  1. 旧版本只是简单的破解(但“老”意味着前5.6,我认为,坦率地说,如果你使用5.6 Perl以前,你有更大的问题)
  2. 每次调用stat (即在大多数情况下无效使用stat )。
  3. 目录名称中的空格问题(这仍然是真的吗?)
  4. (来自brian的答案)可以返回不存在的文件名:

     $ perl -le 'print glob "{ab}{cd}"' 

readdir优点:

  1. (从brian的答案) opendir返回一个文件句柄,你可以在程序中传递(和重用),但glob只是返回一个列表
  2. (来自brian的答案) readdir是一个适当的迭代器,并提供函数rewinddirseekdirtelldir
  3. 更快? (根据上面一些glob的特点来猜测,我并不是很担心这个优化级别,但是这是一个理论上的专家)。
  4. glob更less出现边界错误?
  5. 读取一切(dotfiles也是默认)(这也是一个骗局)
  6. 可能会说服你不要命名一个文件0 (一个con也 – 见布拉德的答案)
  7. 任何人? Bueller? Bueller?

readdir缺点:

  1. 如果您不记得预先指定目录名称,那么当您尝试执行文件testing或复制项目或编辑项目或…
  2. 如果你不记得要清理掉...项目,你得到一点,当你计数项目,或尝试recursion地走下文件树或…
  3. 我有没有提到预先的目录名称? (一个旁注,但我第一次发布Perl初学者的邮件列表是一个经典的,“为什么这个代码涉及filetests不能正常工作?”与这个问题有关的问题。显然,我还是很苦恼。
  4. 项目没有特定的顺序返回。 这意味着您经常需要记住以某种方式对其进行sorting。 (这可能是一个专业,如果这意味着更多的速度,如果这意味着你真的如何,如果你需要对项目进行sorting)。 编辑 :可怕的小样本,但在Mac上readdir按字母顺序返回项目,不区分大小写。 在Debian框和OpenBSD服务器上,顺序完全是随机的。 我用苹果内置的Perl(5.8.8)和我自己编译的5.10.1testing了Mac。 Debian框是5.10.0,与OpenBSD机一样。 我不知道这是一个文件系统问题,而不是Perl?
  5. 读取一切(dotfiles)默认(这也是一个专业人士)
  6. 不一定很好地处理一个名为0的文件(请参阅专业人士 – 请参阅Brad的答案)

你错过了它们之间最重要,最大的区别: glob给你一个列表,但是opendir给你一个目录句柄。 您可以传递该目录句柄以让其他对象或子例程使用它。 使用目录句柄,子程序或对象不必知道它来自哪里,还有谁在使用它,等等:

  sub use_any_dir_handle { my( $dh ) = @_; rewinddir $dh; ...do some filtering... return \@files; } 

用dirhandle,你有一个可控的迭代器,你可以在seekdir移动,但是使用glob就可以得到下一个项目。

与其他任何东西一样,成本和收益只有适用于特定的环境才有意义。 它们不存在于特定用途之外。 你们有很好的分歧,但是如果不知道你们想做什么,我就不会把这些分歧分类。

其他一些事情要记住:

  • 你可以用opendir来实现你自己的glob,但是不能用其他的方法。

  • glob使用自己的通配符语法,这就是你所得到的。

  • glob可以返回不存在的文件名:

     $ perl -le 'print glob "{ab}{cd}"' 

glob优点:可以返回不存在的“文件名”:

 my @deck = List::Util::shuffle glob "{A,K,Q,J,10,9,8,7,6,5,4,3,2}{\x{2660},\x{2665},\x{2666},\x{2663}}"; while (my @hand = splice @deck,0,13) { say join ",", @hand; } __END__ 6♥,8♠,7♠,Q♠,K♣,Q♦,A♣,3♦,6♦,5♥,10♣,Q♣,2♠ 2♥,2♣,K♥,A♥,8♦,6♠,8♣,10♠,10♥,5♣,3♥,Q♥,K♦ 5♠,5♦,J♣,J♥,J♦,9♠,2♦,8♥,9♣,4♥,10♦,6♣,3♠ 3♣,A♦,K♠,4♦,7♣,4♣,A♠,4♠,7♥,J♠,9♥,7♦,9♦ 

这是opendirreaddir的缺点。

 { open my $file, '>', 0; print {$file} 'Breaks while( readdir ){ ... }' } opendir my $dir, '.'; my $a = 0; ++$a for readdir $dir; print $a, "\n"; rewinddir $dir; my $b = 0; ++$b while readdir $dir; print $b, "\n"; 

你会期望代码将打印两次相同的数字,但它不是因为有一个名称为0的文件。 在我的电脑上打印251188 ,用Perl v5.10.0和v5.10.1进行testing

这个问题也使得它只是打印出一堆空行,无论文件0的存在如何:

 use 5.10.0; opendir my $dir, '.'; say while readdir $dir; 

在这里总是工作得很好:

 use 5.10.0; my $a = 0; ++$a for glob '*'; say $a; my $b = 0; ++$b while glob '*'; say $b; say for glob '*'; say while glob '*'; 

我解决了这些问题,并发送了一个补丁,使其成为Perl v5.11.2,所以这个补丁在Perl v5.12.0出现的时候能正常工作。

我的修复转换这个:

 while( readdir $dir ){ ... } 

进入这个:

 while( defined( $_ = readdir $dir ){ ...} 

这使得它的工作方式和read文件一样。 其实它是相同的代码,我只是增加了另一个元素的相应的if语句。

glob可以方便地读取给定固定深度的所有子目录,如glob "*/*/*" 。 我几次发现这个方便。

那么,你几乎覆盖了它。 考虑到所有这些,当我把一个快速的一次性脚本扔到一起时,我倾向于使用glob ,而且它的行为正是我想要的,并且在正在进行的生产代码或库中使用opendirreaddir ,我可以花时间更清晰,更干净的代码是有帮助的。

对于小而简单的事情,我更喜欢glob 。 就在那一天,我用它和一个二十行perl脚本来重build我的音乐库的很大一部分。 glob ,但是,有一个很奇怪的名字。 通配? 就一个名字而言,这并不直观。

我对readdir最大的readdir就是它以一种对大多数人来说有点奇怪的方式来对待一个目录。 通常,程序员不会将目录视为stream,而是将其视为资源或列表,这是由glob提供的。 名称越好,function越好,但界面仍然有些不尽人意的地方。

这是一个非常全面的名单。 readdir (和readdir + grep )的开销比glob ,所以如果你需要分析大量的目录,这对readdir来说是一个readdirselect。

全球优点:

3)不需要手动将目录名添加到项目上

例外:

 say for glob "*"; --output:-- 1perl.pl 2perl.pl 2perl.pl.bak 3perl.pl 3perl.pl.bak 4perl.pl data.txt data1.txt data2.txt data2.txt.out 

据我所知, glob的规则是:您必须提供目录的完整path以获取完整path。 Perl文档似乎没有提到,也没有在这里的任何职位。

这意味着当你只需要文件名(而不是完整path)时,可以使用glob代替readdir ,并且不需要隐藏文件返回,即以'。'开始。 例如,

 chdir ("../.."); say for glob("*"); 

首先,做一些阅读。 第9.6章。 的Perl Cookbook概述了我想要得到的好点,就在讨论标题下。

其次,在你的Perl目录下searchglobdosglob 。 虽然可以使用许多不同的来源(获取文件列表的方式),但是我指出你使用dosglob的原因是,如果你碰巧在Windows平台上(并使用dosglob解决scheme),那实际上是使用opendir / readdir / closedir 。 其他版本使用内置的shell命令或预编译的操作系统特定的可执行文件。

如果你知道你正在瞄准一个特定的平台,你可以使用这个信息,以利于您。 仅供参考,我在“草莓Perl便携版”5.12.2上对此进行了研究,所以对于较新的或原始版本的Perl,可能会有所不同。

在类似的说明中, File::Slurp read_dir有一个名为read_dir的函数。

由于我在脚本中使用了File::Slurp read_dir的其他函数,因此read_dir也成为一种习惯。

它也有以下选项: err_modeprefixkeep_dot_dot