【问题标题】:Perl cannot stat $_Perl 无法统计 $_
【发布时间】:2018-01-20 14:09:53
【问题描述】:

我想要目录中每个文件的最后修改时间。为了确保我的循环正常工作,我打印 $_ 并看到目录的文件名:

for ( @Files ) {

    opendir( D, $path . '\/' . $_ ) or die "$!";
    my @textfiles = grep { ! /^\.{1,2}$/ } readdir( D );

    for ( @textfiles ) {

        #    print "$_\n";   <----the file names.
        my $epoch_timestamp = ( stat( $_ ) )[9];
        print "$epoch_timestamp\n";
    }

我收到此错误

在连接 (.) 或字符串中使用未初始化的值 $epoch_timestamp

我做错了什么?

【问题讨论】:

  • 我通常建议使用glob 而不是readdir,因为它会返回文件的完整路径并允许您进行shellglob 匹配,而不是使用grep 进行正则表达式。在很多种情况下就足够了。

标签: perl


【解决方案1】:

readdir 仅返回文件名。如果您当前的工作目录不同,那么您必须像使用 opendir 的参数一样构建完整路径。最简单的方法是在for 循环的列表中使用map

我很担心你的说法

opendir( D, $path . '\/' . $_ ) or die "$!";

从字面上看,这会将\/ 置于$path$_ 之间。我认为您只需要/,但使用

插入变量是最简单的
opendir( D, "$path/$_" ) or die "$!";

但是$_ 来自数组@Files。如果这些确实是文件名,那么您的 opendir 将失败。它们需要是目录名称

在我的解决方案中,我将变量 $dir 构建为

my $dir = "$path/$_"

以便它可以用于调用opendir 以及在以下for 循环中构建文件的完整路径

请注意,我还使用了词汇目录句柄my $dh,它远远优于全局句柄D

for ( @Files ) {

    my $dir = "$path/$_";

    opendir my $dh, $dir or die $!;
    my @textfiles = grep { ! /^\.{1,2}$/ } readdir $dh;

    for ( map { "$dir/$_" } @textfiles ) {

        #    print "$_\n";   <----the file names.
        my $epoch_timestamp = ( stat( $_ ) )[9];

        print "$epoch_timestamp\n";
    }

【讨论】:

  • 所以我的 $_ 是这种格式:\\client\Z\folder\folder2\file.txt 我成功地构建了完整路径,然后对变量执行 stat 命令。我仍然得到“在 $epoch_timestamp 上使用初始化值”。
  • 好的,开始工作了。出于某种原因,这有效: my $f=stat($filepath)->mtime;但这不起作用:我的 $epoch_timestamp = ( stat( $_ ) )[9];
【解决方案2】:

或者,除了上述完美答案之外,您还可以使用一些模块,让您的生活更轻松。 :) 喜欢:Path::Tiny[1]

use 5.014;
use warnings;

use Path::Tiny;

my $path = path('/etc');
my @Files = qw(defaults cups ssl);

for my $dir (@Files) {
    my @textfiles = $path->child($dir)->children;
    for my $file (@textfiles) {
        say "$file: ", $file->stat->mtime;
    }
}

当然,上面的嵌套循环可以写成

for my $dir (@Files) {
    my @textfiles = $path->child($dir)->children;
    say "$_: ", $_->stat->mtime for (@textfiles);
}

也不需要将文件列表存储到@textfiles,因此可以简化为:

for my $dir (@Files) {
    say "$_: ", $_->stat->mtime for ( $path->child($dir)->children );
}

  1. Path::Tiny 在出错时方便地抛出一个干净的异常消息。

【讨论】:

    【解决方案3】:

    readdir 只返回目录中文件的名称。您需要向stat 提供文件的限定路径。

    my $dir_qfn = ...;
    opendir(my $dh, $dir_qfn)
        or do {
            warn("Can't read dir \"$dir_qfn\": $!\n");
            next;
        };
    
    while (defined( my $fn = readdir($dh) )) {
       next if $fn =~ /^\.\.?\z/;
    
       my $qfn = "$dir_qfn/$fn";
       my $mtime = ( stat($qfn) )[9];
       defined($mtime)
           or do {
               warn("Can't stat file \"$file_qfn\": $!\n");
               next;
           };
    
       ...
    }
    

    【讨论】:

      【解决方案4】:

      改用glob

      my $dir = ...;
      
      my %ts =
          map { $_ => (stat $_)[9] }
              grep { !m{/\.\.?\z} }    #/
                  glob "\Q$dir\E/{*,.*}";
      
      say "ts{$_} => $_" for sort keys %ts;
      

      我使用散列 name => timestamp 在数据结构中收集两者。模式$dir/{*,.*} 也用于捕获点文件,或者它只是$dir/*

      grep 过滤掉... 文件名,在m{..} 匹配的路径中找到。它的模式需要\Q..\E 来防止具有特定目录名称的注入错误。它还可以转义空格,因此不需要带有:bsd_glob选项的File::Glob。感谢ikegami cmets。

      如果您希望一次处理一个文件,请使用glob 检索列表,然后遍历它。

      【讨论】:

      • @ikegami 啊,对,你已经告诉我了!会调整。谢谢,并进行编辑。我“内联”过滤以不通过列表两次,但这是没有意义的,因为它更干净而且可能根本不会变慢(其中有一个if 分支增加了map),除了非常长名单。
      • 这是 IO 驱动的。使用grep+map 而不仅仅是map 所产生的额外子调用(实际上比正常的“多调用”)不会有所作为,并且非常值得额外的可读性(尤其是在 SO 答案中)。
      猜你喜欢
      • 2016-08-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-06
      相关资源
      最近更新 更多