使用Perl删除目录及其子目录中指定扩展名的文件

3
我是一个 Perl 新手。我正在尝试删除目录 A 及其所有子目录 B、C 中特定扩展名的所有文件。我已经学会了如何在给定的目录中这样做,但不是递归地。下面的代码可以在 A 目录中完成任务,但不能在 B、C 子目录中完成。
use strict;    
use warnings;    
my $dir = "~/A/";    
unlink glob "$dir/*.log";

我已经尝试过使用。
use strict;
use warnings;
use File::Find;
my $dir = "~/A";
find(\&wanted, $dir);
sub wanted { 
unlink glob "*.log";
}

但是我收到了一条信息:无法统计~/A: 没有这个文件或目录。虽然该目录存在。有什么提示吗? 马里奥

为什么我的Perl脚本在“~/”上失败,但使用“$ENV{HOME}”却可以工作? - devnull
7个回答

5
尝试使用$ENV{"HOME"}代替~,因为~是特定于shell的。
use strict;    
use warnings;    
my $dir = "$ENV{HOME}/A";
unlink glob "$dir/*.log";

1
@mariodrumblue 为什么这被标记为答案?这并没有解决在 B,C子目录中unlink日志文件的问题。 - chrsblck
@chrsblck。实际上它修复了子目录B和C中取消链接的问题。至少对我来说是这样。这就是为什么我将其标记为答案的原因。 - mariodrumblue
1
@mariodrumblue 是的,它可以“完成工作”,但效果差劲。File::Find会从给定路径递归遍历您的目录树。因此,您应该按设计来使用它。否则,您会遇到与 @mirkobrankovic 的答案相同的问题。我对其进行了清理。 - chrsblck
@chrsblck。我同意,它能完成任务但是很糟糕。我在下面发布我的新版本,基于对此主题发表的评论。它应该会更好,并展示如何同时为多个扩展名而不仅仅是日志进行操作。希望代码可以正常工作,并且对其他新手有用。use strict; use warnings;``use File :: Find;我的目录是"$ENV{HOME} / A"; 将会查找(\&wanted,$dir);子要素:想要(m /\。(log | tex | mat | eps | tif | dta)$ /和做{ unlink $ 或警告qq |无法取消文件$ \ n | } } - mariodrumblue

4
在您的第二个脚本中,在find函数内部不要再进行另一个搜索,因为该函数已经使用递归遍历了树形结构。只需比较文件是否为日志文件并删除即可。一行代码就可以实现:
perl -MFile::Find -e '
    find( 
        sub { m/\.log$/ and do { unlink $_ or warn qq|Could not unlink file _$\n| } 
        }, 
        shift 
    )
' .

它接受一个参数,在我的例子中是.,表示从当前目录开始搜索。

谢谢,自从我今天开始学习Perl以来,我对你的脚本还有一些不太理解的地方,但是我会在接下来的几天里尝试着去理解它。 - mariodrumblue
2
我喜欢这个答案。其他一些答案和原始问题对File::find的作用和glob的作用感到困惑。File::find访问起始目录中的所有文件和目录,因此无需使用glob。作为一个实验,我建议@mariodrumblue(和其他人)尝试使用原始脚本,但将“unlink glob“*.log”;”替换为“print“unlink glob *.log for $File::Find::name\n””。 - AdrianHHH
@AdrianHHH,谢谢。将“unlink glob *.log for $File::Find::name\n”替换后我明白了发生了什么。在查找目录时,Glob很不错,但File::find会一路向下遍历到文件,所以不再需要Glob。使用匹配运算符m/就足够了。 - mariodrumblue

1
看起来 Find::File 在处理“~”符号时出现了问题,因为当我尝试用例如 /root/ 替换它时,它可以正常工作:所以像 @mpapec 建议的那样将其更改为 $ENV{HOME}。
use strict;
use warnings;
use File::Find;
my $dir = "$ENV{HOME}/A";
find(\&wanted, $dir);
sub wanted {
unlink glob "*.log";
}

1
这是一个糟糕的例子:globFile::Find 的组合。 - chrsblck

1
你说得对,glob 不会递归子目录。
我会运行以下代码 as-is,这样你就可以看到它在做什么。一旦你理解了,你可以关闭 $DEBUG 或将其从代码中删除。
#!/usr/bin/perl

use warnings;
use strict;
use File::Find;

my $path = "$ENV{HOME}/A";
my $DEBUG = 1;

find(\&wanted, $path);

sub wanted {
    return if ! -e; 

    my $file = $File::Find::name;

    if ($DEBUG) {
        if( $file =~ /\.log$/ ) { 
            print "Log file found: $file\n"
        } else {
            print "Non-log file found: $file\n";
        }   
    } else {
        # anything that ends with '.log'
        unlink $file if $file =~ /\.log$/;
    }   
}

1

如果你已经在使用find命令,我建议你不要再使用glob。最好直接找到你想要的文件并将它们删除:

use strict;
use warnings;
use File::Find;
use Env qw(HOME);

use constant {
    SUFFIX_LIST => qr/\.(log|foo|bar)$/,
    DIR_TO_CHECK => $HOME,
};

@file_list;

find ( sub {
    return unless -f;
    return unless $_ ~= SUFFIX_LIST;
    push @file_list, $File::Find::name;
}, DIR_TO_CHECK );

unlink @file_list;

我定义了一个正则表达式(即qr/.../),它定义了我感兴趣的后缀列表。我将常量SUFFIX_LIST设置为这个正则表达式。如果我的文件名与这个正则表达式匹配,那么这是我想要删除的文件。
我定义了一个@file_list,主要是出于习惯和find的工作方式。我不是find的忠实粉丝,但这就是我们所拥有的。问题在于,find希望你把所有的代码都放在find子例程中,这是一种不好的实践。为了解决这个问题,我让我的find子例程将我想要的文件推入一个数组中,然后对该数组进行操作。
在这个特定的程序中,由于代码非常简短,我可以直接在find中执行unlink。然而,大多数情况下,使用这个技巧会更好些。 find函数使用两个特殊的 包变量$File::Find::name$file::Find::dir。第一个是文件名和完整路径,以给定给find命令的目录名开头。第二个是目录名(完整路径)。find函数还将$_设置为当前文件名。由于find实际上在文件所在的目录中,因此$_上没有目录名称,并且可以用于测试该文件。

我进行了两个测试:1)这是一个文件吗?2)这个文件的名称是否以我感兴趣的后缀之一结尾。(请注意,对于第一个测试,我可以简单地使用unless -f,而对于第二个测试,我必须指定$_变量。)

如果文件是一个文件并且具有正确的后缀,则将其推入我的@file_list数组中。

我更喜欢将所需的子程序嵌入到我的find命令中。这样可以使函数与影响它的代码保持在一起。以下两者是等价的:

find ( sub {
    return unless -f;
    return unless $_ ~= SUFFIX_LIST;
    push @file_list, $File::Find::name;
}, DIR_TO_CHECK );

并且

find (\&wanted, DIR_TO_CHECK );

sub wanted {
    return unless -f;
    return unless $_ ~= SUFFIX_LIST;
    push @file_list, $File::Find::name;
};

我在编程中使用常量来表示真正的常数,这是一个好的编程习惯。Perl常量有一些奇怪的地方,它们没有特殊符号。因此,在使用可能与字符串混淆的常量时,必须小心。
我还使用use Env来获取我想要定义的环境变量,仅限于这些变量。我可以通过$ENV{HOME}结构将它们引入。这取决于你的偏好。 $ENV{..}结构清楚地表明您正在引入一个环境变量。使用use Env更加简洁。

0
你可以使用 opendir / readdir。这是我管理多个具有不同保留期并且可选择指定带或不带正则表达式的文件的解决方案。
#Add directories to be maintained "|" delimited days to keep files.
my @directories_and_retention = (
qq!$ENV{ARCDIR}|3|\\.lok\$!, #be careful
qq!$ENV{APPPATH}/ldap/logs|5!,
qq!$ENV{LOGDIR}/canary|2!,
qq!$ENV{LOGDIR}/metadata|30!,
qq!$ENV{LOGDIR}/archive|45!
);

foreach my $directory (@directories_and_retention) {
        my ($path,$retention_days,$file) = split(/\|/,$directory);

        opendir (DIR, "$path");
        my @logfiles = readdir(DIR);
        closedir (DIR);

        foreach $logfile (@logfiles) {
                next if ($logfile =~ /^\.\./);
                next if ($logfile =~ /^\./);
                next if (-d "$path/$logfile");

                if ($file) {
                        next unless ($logfile =~ /$file/);
                }

                if (-M "$path/$logfile" > $retention_days) {
                        print "$path/$logfile > $retention_days\n";
                        unlink("$path/$logfile");
                }
        }
}

0

你是在使用Linux吗?如果是的话,我有一个备选方案可能会有所帮助。我假设问题是“我需要递归删除所有特定扩展名的文件”,而没有说明所需语言。如果这是更大工作的一部分,请忽略我的答案;如果你只是在进行一些管理工作,那么这个方案可能会起作用:

find . -type f -name "*.ext" -exec rm {} \;

这将查找当前目录及其子目录中的所有文件,然后将它们的路径传递给rm命令。


我正在使用Mac OS X。我一直在编写基本的shell脚本,但现在我想学习Perl。无论如何,谢谢你的建议! - mariodrumblue
没问题!希望将来它会派上用场。 - chooban

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接