在Perl中迭代目录,获得可自省的对象作为结果



我即将开始一个脚本,可能有一些文件查找和操作,所以我想我应该看看一些软件包,可以帮助我;大多数情况下,我希望迭代(或搜索)的结果作为对象返回,这些对象将具有(基本)名称、路径、文件大小、uid、修改时间等某种属性。

问题是,我不经常这样做,而且容易忘记api;当发生这种情况时,我宁愿让代码在示例目录中运行,并转储对象中的所有属性,这样我就可以提醒自己哪里有可用的内容(显然,我想要"转储",以避免编写自定义打印输出)。但是,我知道以下内容:

列出对象的所有方法
"开箱即用的Perl不做对象自省。像Moose这样的类包装器将自省作为其实现的一部分,但是Perl的内置对象支持比这要原始得多。"

不管怎样,我看了看:

  • " Perl中的文件和目录处理- Perl初学者网站" http://perl-begin.org/topics/files-and-directories/

…并开始研究那里提到的库(也有相关链接:rjbs的标题:Perl文件查找器的速度)。

所以,首先,File::Find::Object似乎对我有用;这段代码:

use Data::Dumper;
@targetDirsToScan = ("./");
use File::Find::Object;
my $tree = File::Find::Object->new({}, @targetDirsToScan);
while (my $robh = $tree->next_obj()) {
  #print $robh ."n"; # prints File::Find::Object::Result=HASH(0xa146a58)}
  print Dumper($robh) ."n";
}

…打印:

# $VAR1 = bless( {
#                  'stat_ret' => [
#                                  2054,
#                                  429937,
#                                  16877,
#                                  5,
#                                  1000,
#                                  1000,
#                                  0,
#                                  '4096',
#                                  1405194147,
#                                  1405194139,
#                                  1405194139,
#                                  4096,
#                                  8
#                                ],
#                  'base' => '.',
#                  'is_link' => '',
#                  'is_dir' => 1,
#                  'path' => '.',
#                  'dir_components' => [],
#                  'is_file' => ''
#                }, 'File::Find::Object::Result' );
# $VAR1 = bless( {
#                  'base' => '.',
#                  'is_link' => '',
#                  'is_dir' => '',
#                  'path' => './test.blg',
#                  'is_file' => 1,
#                  'stat_ret' => [
#                                  2054,
#                                  423870,
#                                  33188,
#                                  1,
#                                  1000,
#                                  1000,
#                                  0,
#                                  '358',
#                                  1404972637,
#                                  1394828707,
#                                  1394828707,
#                                  4096,
#                                  8
#                                ],
#                  'basename' => 'test.blg',
#                  'dir_components' => []

…这主要是我想要的,除了stat结果是一个数组,我必须知道它的布局(($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) stat - perldoc.perl.org)来理解打印输出。

然后我研究了IO::All,我喜欢它,因为它有utf-8处理(而且,说,套接字功能,这对我在同一脚本中执行不相关的任务很有用);我想用这个包裹代替问题是,我很难发现返回对象中的可用字段是什么;例如:

use Data::Dumper;
@targetDirsToScan = ("./");
use IO::All -utf8;
$io = io(@targetDirsToScan);
@contents = $io->all(0);
for my $contentry ( @contents ) {
  #print Dumper($contentry) ."n"; 
  # $VAR1 = bless( *Symbol::GEN298, 'IO::All::File' );
  # $VAR1 = bless( *Symbol::GEN307, 'IO::All::Dir' ); ...
  #print $contentry->uid . " -/- " . $contentry->mtime . "n";
  # https://stackoverflow.com/q/24717210/printing-ret-of-ioall-w-datadumper
  print Dumper %{*$contentry}; # doesn't list uid
}

…我得到这样的打印输出:

# $VAR1 = {
#           '_utf8' => 1,
#           'constructor' => sub { "DUMMY" },
#           'is_open' => 0,
#           'io_handle' => undef,
#           'name' => './test.blg',
#           '_encoding' => 'utf8',
#           'package' => 'IO::All'
#         };
# $VAR1 = {
#           '_utf8' => 1,
#           'constructor' => sub { "DUMMY" },
#           'mode' => undef,
#           'name' => './testdir',
#           'package' => 'IO::All',
#           'is_absolute' => 0,
#           'io_handle' => undef,
#           'is_open' => 0,
#           '_assert' => 0,
#           '_encoding' => 'utf8'

…它显然没有显示mtime等属性——即使它们存在(如果你取消相应的打印行注释,你可以看到它们)。

我也尝试过Data::Printer的(我如何在Perl中执行自省?)p()函数-它打印与Dumper完全相同的字段。我还尝试使用print Dumper %{ref ($contentry) . "::"};(列出对象的所有方法- perlmonks.org),它打印的内容如下:

'O_SEQUENTIAL' => *IO::All::File::O_SEQUENTIAL,
'mtime' => *IO::All::File::mtime,
'DESTROY' => *IO::All::File::DESTROY,
...
'deep' => *IO::All::Dir::deep,
'uid' => *IO::All::Dir::uid,
'name' => *IO::All::Dir::name,
...

…但是如果您事先使用print $contentry->uid ...行,则只能;否则,它们不会被列出!我想这和这个有关:

introspection -如何在Perl中列出给定对象或包的可用方法?# 911294
一般来说,您不能使用像Perl这样的动态语言这样做。包可能会定义一些您可以找到的方法,但它也可以动态地创建一些方法,这些方法在使用之前没有定义。此外,即使调用(有效的)方法也可能无法定义它。这就是动态语言的优点。:)

仍然打印字段的名称和类型—我想要字段的名称和值。

所以,我想我的主要问题是-我如何转储IO::All结果,以便所有字段(包括stat字段)都与其名称和值一起打印出来(主要是File::Find::Object的情况)?

(我注意到IO::All的结果可以是IO::All::File的类型,但是它的文档遵从"See IO::All",它根本没有明确地讨论IO::All::File。我想,如果我可以"cast"%{*$contentry}IO::All::File,也许然后mtime等字段将被打印-但是这样的"cast"是可能的吗?

如果这是有问题的,是否有其他的包,这将允许内省的目录迭代结果的打印输出-但与个别stat属性命名字段?

Perl进行自省,对象将告诉您它是什么类型的对象。

if ( $object->isa("Foo::Bar") ) {
    say "Object is of a class of Foo::Bar, or is a subclass of Foo::Bar.";
}
if ( ref $object eq "Foo::Bar" ) {
    say "Object is of the class Foo::Bar.";
}
else {
    say "Object isn't a Foo::Bar object, but may be a subclass of Foo::Bar";
}

你还可以查看一个对象是否可以做某事:

if ( $object->can("quack") ) {
    say "Object looks like a duck!";
}

Perl不能直接做的是给你一个特定对象可以做的所有方法的列表。

你也许可以改变一些方法。Perl对象存储在符号表中的包名称空间中。类通过Perl子例程实现。可以遍历包名称空间,然后找到所有子例程。

然而,我可以看到几个问题。首先,私有方法(您不应该使用的方法)和非方法子例程也将包括在内。根本分不清哪个是哪个。此外,父方法也不会被列出。

许多语言都可以为它们的对象生成这样的方法列表(我相信Python和Ruby都可以),但它们通常只给你一个列表,而不解释这些方法的作用。例如,File::Find::Object::Result(由File::Find::Objectnext_obj方法返回)有一个base方法。它是做什么的?也许它就像basename,给我文件名。不,它就像dirname,给了我目录的名字。

同样,一些语言可以为对象和描述提供这些方法的列表。然而,这些描述依赖于程序员来维护并确保它们是正确的。不能保证。

Perl没有自省功能,但是存储在CPAN中的所有Perl模块都必须通过POD嵌入的文档进行记录,并且可以从命令行打印:

$ perldoc File::Find::Object

这是您在CPAN页面,http://Perldoc.perl.org和ActiveState的Perl文档中看到的文档。

还不错。这不是真正的自省,但文档通常都很好。毕竟,如果文档很糟糕,我可能一开始就不会安装那个模块。我一直使用perldoc。我几乎不能记住我孩子的名字,更不用说我几个月没有使用过的Perl类的使用方法了,但是我发现使用perldoc非常有效。

你不应该做的是使用Data::Dumper来转储对象,并试图找出它们包含什么和可能的方法。一些cleaver程序员正在使用Inside-Out Objects来阻止偷窥者。

所以,Perl不像某些语言那样列出特定类的方法,但是perldoc非常接近您所需要的。我很久没有使用File::Find::Object了,但是通过perldoc,我可能可以毫不费力地编写这样一个程序。

正如我对您上一个问题的回答,依赖Perl中的对象内部不是一个好主意。而是直接调用方法。

如果IO::All没有提供给你所需信息的方法,你可以为它编写自己的方法,使用IO::All提供的文档方法来组装这些信息…

use IO::All;
# Define a new method for IO::All::Base to use, but
# define it in a lexical variable!
#
my $dump_info = sub {
   use Data::Dumper ();
   my $self = shift;
   local $Data::Dumper::Terse    = 1;
   local $Data::Dumper::Sortkeys = 1;
   return Data::Dumper::Dumper {
      name    => $self->name,
      mtime   => $self->mtime,
      mode    => $self->mode,
      ctime   => $self->ctime,
   };
};
$io = io('/tmp');
for my $file ( $io->all(0) ) {
   print $file->$dump_info();
}

好吧,这是一个练习(和提醒我);下面是一些代码,我试图为所有stat字段定义一个类(File::Find::Object::StatObj)的访问器字段。然后,我从替换Perl中的类("覆盖"/"扩展")中获得IO::All::File的hack。具有相同名称的类)?,其中增加了一个mtimef字段,对应mtime,只是作为提醒。

然后,只是为了看看两个库之间可以有什么样的接口,我让IO::All进行迭代;将当前文件路径传递给File::Find::Object,从中我们得到一个File::Find::Object::Result——它被"黑"了,也显示了File::Find::Object::StatObj;但只有在调用被黑客攻击的Result的full_components(这也可能是一个单独的函数)之后才生成。注意,在本例中,您不会得到File::Find::Object::Resultfull_components/dir_components——因为显然不是File::Find::Object在进行遍历,而是IO::All。无论如何,结果是这样的:

#  $VAR1 = {
#            '_utf8' => 1,
#            'mtimef' => 1403956165,
#            'constructor' => sub { "DUMMY" },
#            'is_open' => 0,
#            'io_handle' => undef,
#            'name' => 'img/test.png',
#            '_encoding' => 'utf8',
#            'package' => 'IO::All'
#          };
#  img/test.png
# >  - $VAR1 = bless( {
#                   'base' => 'img/test.png',
#                   'is_link' => '',
#                   'is_dir' => '',
#                   'path' => 'img/test.png',
#                   'is_file' => 1,
#                   'stat_ret' => [
#                                   2054,
#                                   426287,
#                                   33188,
#                                   1,
#                                   1000,
#                                   1000,
#                                   0,
#                                   '37242',
#                                   1405023944,
#                                   1403956165,
#                                   1403956165,
#                                   4096,
#                                   80
#                                 ],
#                   'basename' => undef,
#                   'stat_obj' => bless( {
#                                          'blksize' => 4096,
#                                          'ctime' => 1403956165,
#                                          'rdev' => 0,
#                                          'blocks' => 80,
#                                          'uid' => 1000,
#                                          'dev' => 2054,
#                                          'mtime' => 1403956165,
#                                          'mode' => 33188,
#                                          'size' => '37242',
#                                          'nlink' => 1,
#                                          'atime' => 1405023944,
#                                          'ino' => 426287,
#                                          'gid' => 1000
#                                        }, 'File::Find::Object::StatObj' ),
#                   'dir_components' => []
#                 }, 'File::Find::Object::Result' );

我不确定这有多正确,但我喜欢的是我可以忘记字段在哪里;然后我可以重新运行转储程序,并看到我可以通过(*::Result) ->stat_obj->size获得mtime -这似乎有效(这里我只需要读取这些,而不是设置它们)。

无论如何,下面是代码:

use Data::Dumper;
my @targetDirsToScan = ("./");
use IO::All -utf8 ;                          # Turn on utf8 for all io
# try to "replace" the IO::All::File class
{ # https://stackoverflow.com/a/24726797/277826
  package IO::All::File;
  use IO::All::File; # -base; # just do not use `-base` here?!
  # hacks work if directly in /usr/local/share/perl/5.10.1/IO/All/File.pm
  # NB: field is a sub in /usr/local/share/perl/5.10.1/IO/All/Base.pm
  field mtimef => undef; # hack
  sub file {
    my $self = shift;
    bless $self, __PACKAGE__;
    $self->name(shift) if @_;
    $self->mtimef($self->mtime); # hack
    #print("!! *haxx0rz'd* file() reporting inn");
    return $self->_init;
  }
  1;
}
use File::Find::Object;
# based on /usr/local/share/perl/5.10.1/File/Find/Object/Result.pm;
# but inst. from /usr/local/share/perl/5.10.1/File/Find/Object.pm
{
  package File::Find::Object::StatObj;
  use integer;
  use Tie::IxHash;
  #use Data::Dumper;
  sub ordered_hash { # https://stackoverflow.com/a/3001400/277826
    #my (@ar) = @_; #print("# ". join(",",@ar) . "n");
    tie my %hash => 'Tie::IxHash';
    %hash = @_; #print Dumper(%hash);
    %hash
  }
  my $fields = ordered_hash(
        # from http://perldoc.perl.org/functions/stat.html
        (map { $_ => $_ } (qw(
        dev ino mode nlink uid gid rdev size
        atime mtime ctime blksize blocks
        )))
      ); #print Dumper(%{$fields});
  use Class::XSAccessor
      #accessors => %{$fields}, # cannot - is seemingly late
      # ordered_hash gets accepted, but doesn't matter in final dump;
      #accessors => { (map { $_ => $_ } (qw(
      accessors => ordered_hash( (map { $_ => $_ } (qw(
        dev ino mode nlink uid gid rdev size
        atime mtime ctime blksize blocks
      ))) ),
      #))) },
      ;
  use Fcntl qw(:mode);
  sub new
  {
    #my $self = shift;
    my $class = shift;
    my @stat_arr = @_; # the rest
    my $ic = 0;
    my $self = {};
    bless $self, $class;
    for my $k (keys %{$fields}) {
      $fld = $fields->{$k};
      #print "$ic '$k' '$fld' ".join(", ",$stat_arr[$ic])." ; ";
      $self->$fld($stat_arr[$ic]);
      $ic++;
    }
    #print "n";
    return $self;
  }
  1;
}
# try to "replace" the File::Find::Object::Result
{
  package File::Find::Object::Result;
  use File::Find::Object::Result;
  #use File::Find::Object::StatObj; # no, has no file!
  use Class::XSAccessor replace => 1,
      accessors => {
          (map { $_ => $_ } (qw(
          base
          basename
          is_dir
          is_file
          is_link
          path
          dir_components
          stat_ret
          stat_obj
          )))
      }
      ;
  #use Fcntl qw(:mode);
  #sub new # never gets called
  sub full_components
  {
    my $self = shift; #print("NEWCOMPn");
    my $sobj = File::Find::Object::StatObj->new(@{$self->stat_ret()});
    $self->stat_obj($sobj); # add stat_obj and its fields
    return
    [
      @{$self->dir_components()},
      ($self->is_dir() ? () : $self->basename()),
    ];
  }
  1;
}
# main script start
my $io = io($targetDirsToScan[0]);
my @contents = $io->all(0);                    # Get all contents of dir
for my $contentry ( @contents ) {
  print Dumper %{*$contentry};
  print $contentry->name . "n"; # img/test.png
  # get a File::Find::Object::Result - must instantiate
  #  a File::Find::Object; just item_obj() will return undef
  #  right after instantiation, so must give it "next";
  # no instantition occurs for $tro, though!
  #my $tffor = File::Find::Object->new({}, ($contentry->name))->next_obj();
  my $tffo = File::Find::Object->new({}, ("./".$contentry->name));
  my $tffos = $tffo->next(); # just a string!
  $tffo->_calc_current_item_obj(); # unfortunately, this will not calculate dir_components ...
  my $tffor = $tffo->item_obj();
  # ->full_components doesn't call new, either!
  # must call full_compoments, to generate the fields
  #  (assign to unused variable triggers it fine)
  # however, $arrref_fullcomp will be empty, because
  #  File::Find::Object seemingly calcs dir_components only
  #  if it is traversing a tree...
  $arrref_fullcomp = $tffor->full_components;
  #print("# ".$tffor->stat_obj->size."n"); # seems to work
  print "> ". join(", ", @$arrref_fullcomp) ." - ". Dumper($tffor);
}

相关内容

  • 没有找到相关文章

最新更新