如何在Perl脚本中使用find命令?

如何在Perl脚本中使用find命令?

有人能告诉我为什么find命令总是转到根目录而不是指定的目录$srceDir吗?

my $srceDir = "/mnt/SDrive/SV/Capture Data/";

my $find_cmd = 'find $srceDir -type f -newermt 2013-02-14 ! -newermt 2013-02-15';

open(FIND_FILE, "$find_cmd |");
while(<FIND_FILE>){ 
    next if /^total/; # because we're only interested in real output
    print $_; 
}

答案1

因为您使用单引号而不是双引号。

Perl 没有插入用单引号括起来的变量,所以您要做的是将字符串 '$srceDir' 发送到 shell,该字符串通常会被取消设置(空白),除非您在环境中的某处设置了它。

尝试这个:

my $find_cmd = "find $srceDir -type f -newermt 2013-02-14 ! -newermt 2013-02-15";

或者更好的是这个:

my $find_cmd = sprintf
    'find "%s" -type f -newermt 2013-02-14 ! -newermt 2013-02-15',
    $srceDir;

...关心空间的同时查找命令将在 forked 下执行sh

* 重要备注 *

正如@vonbrand 正确评论的那样:确实提供了很多库来确保您的程序和许多其他事物之间的通信。

对于文件系统操作find,perl 使用File库模块File::Find,其中存在一个小实用程序find2perl,可以将find命令行转换为一个小 perl 脚本:

$ find2perl -type f -mtime -3 ! -mtime -2;
#! /usr/bin/perl -w
    eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
        if 0; #$running_under_some_shell
use strict;
use File::Find ();

# Set the variable $File::Find::dont_use_nlink if you're using AFS,
# since AFS cheats.

# for the convenience of &wanted calls, including -eval statements:
use vars qw/*name *dir *prune/;
*name   = *File::Find::name;
*dir    = *File::Find::dir;
*prune  = *File::Find::prune;

sub wanted;

# Traverse desired filesystems
File::Find::find({wanted => \&wanted}, '.');
exit;

sub wanted {
    my ($dev,$ino,$mode,$nlink,$uid,$gid);
    (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) &&
    -f _ &&
    (int(-M _) < 3) &&
    ! (int(-M _) < 2)
    && print("$name\n");
}

所以你的需求可能会变成这样:

#! /usr/bin/perl -w

my $srceDir   = "/mnt/SDrive/SV/Capture Data/";
my $startDate = "2013-02-14";
my $endDate   = "2013-02-15";

use strict;
use File::Find ();
use POSIX qw|mktime|;

use vars qw/*name *dir *prune/;
*name   = *File::Find::name;
*dir    = *File::Find::dir;
*prune  = *File::Find::prune;

my ($sDay,$eDay)=map {
    my ($year,$month,$day)=split("-",$_);
    (time()-mktime(0,0,0,$day,$month-1,$year-1900))/86400
} ($startDate,$endDate);

sub wanted {
    my ($dev,$ino,$mode,$nlink,$uid,$gid);

    (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) &&
    -f _ &&
    (-M _ < $sDay) &&
    ! (-M _ < $eDay)
    && print("$name\n");
}
File::Find::find({wanted => \&wanted}, $srceDir );

这样做的最大优点是open $fh,"find ...|",它非常稳健;您不必关心文件名中存在的字符(如空格、引号、与号...)。

答案2

如果您为命令提供一个带有 shell 元字符(如空格)的字符串,那么它将被解释为 shell 命令行,这意味着两件事:

  1. 需要执行一个额外的命令(shell 来解析该命令行),这不是很有效。对于某些 shell 实现,这甚至意味着一个额外的过程。
  2. 您需要将特殊字符转义到 shell。

最好是直接执行命令,即通过向命令提供参数列表来执行,而不是要求 shell 分割命令行来构建该参数列表。

另外,除非您使用,否则无法安全地对-print0的输出进行后处理,因为记录用换行符分隔,而换行符是文件名中完全有效的字符,所以就像我在find我对你类似问题的回答,你需要这样写:

my $srceDir = "/mnt/SDrive/SV/Capture Data";
my @find_cmd = ("find", $srceDir, "-type", "f", "-newermt", "14 Feb 2013", "-print0");

open FIND, "-|", @find_cmd;
$/ = "\0"; # set perl's record separator
while (<FIND>) {
  ...
}
close FIND or warn $! ?
  "Error closing find pipe: $!" :
  "find exited with non-zero exit status: $?";

(顺便说一句,find不输出total行,您可能会感到困惑ls)。

相关内容