在 Perl 脚本中使用 File::Find 时忽略整个目录

Posted

技术标签:

【中文标题】在 Perl 脚本中使用 File::Find 时忽略整个目录【英文标题】:Ignore an entire directory when using File::Find in Perl script 【发布时间】:2014-06-18 02:58:43 【问题描述】:

我有一个脚本,它可以扫描每个本地文件系统以查找全球可写文件。任何找到的文件都将写入输出文件。它还使用另一个文件,该文件提供要忽略的文件列表。

我们安装了 Tivoli 监视代理程序,出于某种奇怪的原因,它被设计为在其安装路径中创建具有全局可写权限的每个文件。众所周知,我们对此无能为力,我们想简单地忽略整个目录。

我想我可以使用诸如 /opt/IBM/ITM/* 这样的 glob,但我对如何做到这一点一无所知。

目前我已将目录硬编码到脚本中。这不太理想,但很实用。我更愿意将它放在排除列表中。

在Code Review,有人建议我使用File::Find::prune。不幸的是,这没有奏效。根据我对File::Find::prune 的收集和理解,如果它在/opt/IBM/ITM/.../.../file.txt 找到一个应该被排除的文件,它将跳过整个/opt/IBM/ITM/.../.../ 目录。这很好,但这意味着我需要为/opt/IBM/ITM/ 的每个子目录设置一个排除项。考虑到有多少子目录和子子目录,这将是一项繁琐的工作。

我确实尝试在/opt/IBM/ITM/ 下放置一个全球可写文件并将其添加到排除列表中,但它不起作用。我猜是因为没有先找到它。

脚本:

#!/usr/bin/perl

use warnings;
use strict;
use Fcntl ':mode';
use File::Find;
no warnings 'File::Find';
no warnings 'uninitialized';

my $dir = "/var/log/tivoli/";
my $mtab = "/etc/mtab";
my $permFile = "world_writable_files.txt";
my $tmpFile = "world_writable_files.tmp";
my $exclude = "/usr/local/etc/world_writable_excludes.txt";
my $mask = S_IWUSR | S_IWGRP | S_IWOTH;
my (%excludes, %devNums);
my $errHeader;

# Compile a list of mountpoints that need to be scanned
my @mounts;

open MT, "<$mtab" or die "Cannot open $mtab, $!";

# We only want the local mountpoints
while (<MT>) 
  if ($_ =~ /ext[34]/) 
    chomp;
    my @line = split;
    push(@mounts, $line[1]);
    my @stats = stat($line[1]);
    $devNums$stats[0] = undef;
  


close MT;

# Build a hash from /usr/local/etc/world_writables_excludes.txt
if ((! -e $exclude) || (-z $exclude)) 
  $errHeader = <<HEADER;
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!                                                  !!
!! /usr/local/etc/world_writable_excludes.txt is    !!
!! is missing or empty. This report includes        !!
!! every world-writable file including those which  !!
!! are expected and should be excluded.             !!
!!                                                  !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


HEADER

 else 
  open XCLD, "<$exclude" or die "Cannot open $exclude, $!\n";
  while (<XCLD>) 
    chomp;
    $excludes$_ = 1;
  


sub wanted 
  my @dirStats = stat($File::Find::name);

  # Is it excluded from the report...
  return if exists $excludes$File::Find::name;

  # ...is the Tivoli installation directory...
  return if ($File::Find::name =~ /\b\/ITM\b/);

  # ...in a special directory, ...
  return if ($File::Find::name =~ /^\bsys\b|\bproc\b|\bdev\b$/);

  # ...a regular file, ...
  return unless -f;

  # ...local, ...
  return unless (exists $devNums$dirStats[0]);

  # ...and world writable?
  return unless ($dirStats[2] & $mask) == $mask;

  # If so, add the file to the list of world writable files
  print(WWFILE "$File::Find::name\n");



# Create the output file path if it doesn't already exist.
mkdir($dir or die "Cannot execute mkdir on $dir, $!") unless (-d $dir);

# Create our filehandle for writing our findings
open WWFILE, ">$dir$tmpFile" or die "Cannot open $dir$tmpFile, $!";
print(WWFILE "$errHeader") if ($errHeader);

finddepth(\&wanted, @mounts);

close WWFILE;

# If no world-writable files have been found $tmpFile should be zero-size;
# Delete it so Tivoli won't alert
if (-z "$dir$tmpFile") 
  unlink "$dir$tmpFile";

 else 
  rename("$dir$tmpFile","$dir$permFile") or die "Cannot rename file $dir$tmpFile, $!";


其他地方也有人建议我使用 File::Find::Rule。我宁愿避免这样做,因为我不想完全重写脚本。

正如我所说,上面的脚本有效。不过,我不希望对排除项进行硬编码。弄清楚如何做到这一点也可以让我删除与“特殊”目录的匹配。

【问题讨论】:

【参考方案1】:

要修剪整个目录树,只需在您的 wanted 子目录中设置 $File::Find::prune 值。只要未指定 bydepth,这将起作用:

if ($File::Find::name eq '/opt/IBM/ITM') 
    $File::Find::prune = 1;
    return;

【讨论】:

我看到的问题是$File::Find::prune 只会忽略找到文件的目录。例如,当在 /opt/IBM/ITM/log/ $File::Find::prune will will prevent any more files in /opt/IBM/ITM/log/` 中发现一个文件正在被处理。但是,/opt/IBM/ITM/ 下的所有其他子目录将继续处理,$File::Find::prune 在遇到每个子目录时对其进行操作。 此功能的文档不是很明确,但这不是我观察到的结果。当我使用此功能时,它会完全跳过我指定要修剪的完整目录树。 我想我没看清楚。它似乎按照您的建议工作。我继续将所有我想忽略的目录合并到if ($File:Find::name =~ m^/sys|^/proc|^/dev|^/opt/IBM/ITM)。它似乎返回了预期的结果。

以上是关于在 Perl 脚本中使用 File::Find 时忽略整个目录的主要内容,如果未能解决你的问题,请参考以下文章

没看完_perl的find模块很全面的讲解!!

Perl回调函数和闭包

Perl 无法在 Windows 10 中找到现有目录

在 Matlab 中实时显示 Perl 脚本的输出

从另一个 Perl 脚本内部调用 Perl 搜索和替换命令时不起作用

如何使用多个版本的 Perl 在 Perl 模块中安装脚本?