2013-05-23 2 views
2

Я видел this link используя GlobКак бы я искать через каталог и все подкаталоги рекурсивно с помощью Perl

Это не совсем то, что я хочу сделать, хотя.

Вот мой план. Чтобы искать хотя бы каталог для любых файлов, которые частично соответствуют строке, заданной моей функции как параметр, скажем /home/username/sampledata и строку, скажем data.

Предоставляю пользователю возможность включать флаг при исполнении, обеспечивающий проверку или отсутствие подкаталогов, а в настоящее время по умолчанию скрипт не включает подкаталоги.

Псевдокод для того, который включает в себя подкаталоги, будет выглядеть следующим образом.

массив, что я спасаю пути к файлам в глобальна

@fpaths; 

    foo($dir); 

    sub foo{ 
     get a tmp array of all files 

     for ($i=0 ; $i<@tmp ; $i++) { 
      next if ($tmp[$i]is a hidden file and !$hidden) ; #hidden is a flag too 

      if($tmp[$i] is file) { 
       push (@fpaths, $dir.$tmp[$i]); 
      } 
      if($tmp[$i] is dir) { 
       foo($dir.$tmp[$i]); 
      } 

     } 
    } 

Это выглядит довольно твердый.

То, что я надеюсь достичь, - это массив каждого файла с сохраненным полным именем пути.

Часть, которую я не знаю, как получить список всех файлов. Надеюсь, это можно сделать с помощью glob.

Я смог использовать opendir/readdir, чтобы читать каждый файл, и я мог бы сделать это снова, если бы знал, как проверить, является ли результат файлом или каталогом.

Так что мои вопросы:

  1. Как использовать glob с именем пути, чтобы получить массив всех файлов/подкаталог

  2. Как проверить, если элемент на ранее найденный массиве является каталогом или файлом

Спасибо всем

+4

ли в [File :: Find] (http://perldoc.perl.org/File/Find.html) модуль делать то, что вам нужно? – Barmar

+1

'for ($ i = 0; $ i <@tmp; $ i ++) {...}' обычно записывается 'для моего $ i (0 .. $ # tmp) {...}' – Borodin

ответ

3
  • Я не понимаю, почему glob решает вашу проблему, как проверить, является ли запись в каталоге файлом или каталогом.Если вы используете readdir до этого придерживаться

  • Не забывайте, что вы должны тщательно обрабатывать ссылки, иначе рекурсия никогда не может закончиться

  • Также помните, что readdir возвращает . и .., а также содержимое реального каталога

  • Используйте -f and -d, чтобы проверить, является ли имя узла файлом или каталогом, но помните, что если его loaction не является вашим текущим рабочим каталогом, тогда вы должны полностью квалифицировать его, добавив путь, иначе вы будете говорить о полном LY отличается узел, который, вероятно, не существует

  • Если это не опыт обучения, вы гораздо лучше писать что-то готовое прокатке и испытаны, как File::Find

+0

Да, я я сначала проверяю файлы «Скрытые», они также должны быть исключены в проверке подкаталога. если у меня есть полное имя файла, как я могу выполнить проверку -d/-f? это было бы так: if ($ path -d) print это каталог? –

+1

@ChrisTopher: [Ссылки не связаны со скрытыми файлами.] (Http://www.cyberciti.biz/tips/understanding-unixlinux-symbolic-soft-and-hard-links.html) Прочтите ссылку, с которой я связан в моем ответе или ссылку, с которой вы связались в своем вопросе, чтобы узнать, как использовать '-f' и -d'. – Borodin

+0

Это именно то, что я искал, я не мог сказать, что они были ссылками, Ха, моя рекурсия работает, просто не добавляя к массиву должным образом. Огромное спасибо. Просто начал изучать Perl во вторник, классный язык сценариев. Поэтому я ценю какое-то направление –

9

Я хотел бы использовать File::Find

Обратите внимание, что File::Find::name - это полный путь к данному файлу. К ним относятся каталоги, поскольку они также являются файлами.

Это просто образец для чтения, чтобы выяснить остальную информацию.

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

my $path = "/home/cblack/tests"; 

find(\&wanted, $path); 

sub wanted { 
    return if ! -e; 

    print "$File::Find::name\n" if $File::Find::name =~ /foo/; 
    print "$File::Find::dir\n" if $File::Find::dir =~ /foo/; 
} 

еще лучше, если вы хотите, чтобы подтолкнуть все это к списку вы можете сделать это так:

use File::Find; 

main(); 

sub main { 
    my $path = "/home/cblack/Misc/Tests"; 
    my $dirs = []; 
    my $files= []; 
    my $wanted = sub { _wanted($dirs, $files) }; 

    find($wanted, $path); 
    print "files: @$files\n"; 
    print "dirs: @$dirs\n"; 
} 

sub _wanted { 
    return if ! -e; 
    my ($dirs, $files) = @_; 

    push(@$files, $File::Find::name) if $File::Find::name=~ /foo/; 
    push(@$dirs, $File::Find::dir) if $File::Find::dir =~ /foo/; 
} 
+0

Не совсем понимаю, как вы перемещаетесь по каталогу, вероятно, будет найдено несколько сотен файлов и десяток случайных неизвестных подкаталогов. если какие-либо файлы или файлы в подкаталоге имеют имя, содержащее определенную строку, я хочу, чтобы весь путь был сохранен в массиве. –

+0

@ChrisTopher Я связал 'File :: Find' в первой строке моего сообщения. Вы должны это прочитать. На самом деле первая строка в описании модуля гласит: «Это функции для поиска по деревьям каталогов, выполняющим работу над каждым файлом, подобным команде поиска Unix». И затем далее описывается функция в моем посте, 'find'. Почему бы вам не создать небольшую песочницу для себя, чтобы поиграть? – chrsblck

+0

Будет эксперимент, спасибо –

1

вы можете использовать этот метод рекурсивного поиска файлов, что отдельные конкретные типы файлов,

my @files; 
push @files, list_dir($outputDir); 

sub list_dir { 
     my @dirs = @_; 
     my @files; 
     find({ wanted => sub { push @files, glob "\"$_/*.txt\"" } , no_chdir => 1 }, @dirs); 
     return @files; 
} 
+1

Престижность умной комбинации 'File :: Find' и' glob'. Две тонкости: вы всматриваетесь в вызов любой из функций 'wanted', что означает, что вы тоже бесполезно вызывают' glob() 'on _files_, а не только каталоги. И наоборот, если элемент под рукой - _symlink_ в каталог, вы также клонируете его содержимое (но не рекурсивно), что может быть неожиданным, учитывая, что вы не настраиваете 'find', чтобы следовать символическим ссылкам. Если вам не нужно следовать символическим ссылкам, вы можете сделать свое подтачивание в подпрограмме 'preprocess' вместо этого и вернуть из него пустой список. – mklement0

+1

@ mklement0: Спасибо за тонкости. Вы абсолютно правы об этом –

2

Вдохновленный Nima Soroush's answer, вот обобщенная рекурсивная функция подстановка похожа на Bash 4 в globstar ор который позволяет сопоставлять все уровни поддерева с **.

Примеры:

# Match all *.txt and *.bak files located anywhere in the current 
# directory's subtree. 
globex '**/{*.txt,*.bak}' 

# Find all *.pm files anywhere in the subtrees of the directories in the 
# module search path, @INC; follow symlinks. 
globex '{' . (join ',', @INC) . '}/**/*.pm', { follow => 1 } 

Примечание: В то время как эта функция, которая сочетает в себе File::Find с встроенной glob функции, вероятно, в основном работает, как вы ожидаете, если вы знакомы с поведением glob «s, там есть много тонкостей вокруг сортировки и поведения символьной ссылки - см. комментарии внизу.

Отклонение от glob() состоит в том, что пробел в заданном шаблоне считается частью шаблона; для указания нескольких шаблонов, передачи их в виде отдельных аргументов шаблона или использования выражения скобки, как в примере выше.

Исходный код

sub globex { 

    use File::Find; 
    use File::Spec; 
    use File::Basename; 
    use File::Glob qw/bsd_glob GLOB_BRACE GLOB_NOMAGIC GLOB_QUOTE GLOB_TILDE GLOB_ALPHASORT/; 

    my @patterns = @_; 
    # Set the flags to use with bsd_glob() to emulate default glob() behavior. 
    my $globflags = GLOB_BRACE | GLOB_NOMAGIC | GLOB_QUOTE | GLOB_TILDE | GLOB_ALPHASORT; 
    my $followsymlinks; 
    my $includehiddendirs; 
    if (ref($patterns[-1]) eq 'HASH') { 
    my $opthash = pop @patterns; 
    $followsymlinks = $opthash->{follow}; 
    $includehiddendirs = $opthash->{hiddendirs}; 
    } 
    unless (@patterns) { return }; 

    my @matches; 
    my $ensuredot; 
    my $removedot; 
    # Use fc(), the casefolding function for case-insensitive comparison, if available. 
    my $cmpfunc = defined &CORE::fc ? \&CORE::fc : \&CORE::lc; 

    for (@patterns) { 
    my ($startdir, $anywhereglob) = split '(?:^|/)\*\*(?:/|$)'; 
    if (defined $anywhereglob) { # recursive glob 
     if ($startdir) { 
     $ensuredot = 1 if m'\./'; # if pattern starts with '.', ensure it is prepended to all results 
     } elsif (m'^/') { # pattern starts with root dir, '/' 
     $startdir = '/'; 
     } else { # pattern starts with '**'; must start recursion with '.', but remove it from results 
     $removedot = 1; 
     $startdir = '.'; 
     } 
     unless ($anywhereglob) { $anywhereglob = '*'; } 
     my $terminator = m'/$' ? '/' : ''; 
     # Apply glob() to the start dir. as well, as it may be a pattern itself. 
     my @startdirs = bsd_glob $startdir, $globflags or next; 
     find({ 
      wanted => sub { 
      # Ignore symlinks, unless told otherwise. 
      unless ($followsymlinks) { -l $File::Find::name and return; } 
      # Ignore non-directories and '..'; we only operate on 
      # subdirectories, where we do our own globbing. 
      ($_ ne '..' and -d) or return; 
      # Skip hidden dirs., unless told otherwise. 
      unless ($includehiddendirs) { return if basename($_) =~ m'^\..'; } 
      my $globraw; 
      # Glob without './', if it wasn't part of the input pattern. 
      if ($removedot and m'^\./(.+)$') { 
       $_ = $1; 
      } 
      $globraw = File::Spec->catfile($_, $anywhereglob); 
      # Ensure a './' prefix, if the input pattern had it. 
      # Note that File::Spec->catfile() removes it. 
      if($ensuredot) { 
       $globraw = './' . $globraw if $globraw !~ m'\./'; 
      } 
      push @matches, bsd_glob $globraw . $terminator, $globflags; 
      }, 
      no_chdir => 1, 
      follow_fast => $followsymlinks, follow_skip => 2, 
      # Pre-sort the items case-insensitively so that subdirs. are processed in sort order. 
      # NOTE: Unfortunately, the preprocess sub is only called if follow_fast (or follow) are FALSE. 
      preprocess => sub { return sort { &$cmpfunc($a) cmp &$cmpfunc($b) } @_; } 
     }, 
     @startdirs); 
    } else { # simple glob 
     push @matches, bsd_glob($_, $globflags); 
    } 
    } 
    return @matches; 
} 

Комментарии

SYNOPSIS 
    globex PATTERNLIST[, \%options] 

DESCRIPTION 
    Extends the standard glob() function with support for recursive globbing. 
    Prepend '**/' to the part of the pattern that should match anywhere in the 
    subtree or end the pattern with '**' to match all files and dirs. in the 
    subtree, similar to Bash's `globstar` option. 

    A pattern that doesn't contain '**' is passed to the regular glob() 
    function. 
    While you can use brace expressions such as {a,b}, using '**' INSIDE 
    such an expression is NOT supported, and will be treated as just '*'. 
    Unlike with glob(), whitespace in a pattern is considered part of that 
    pattern; use separate pattern arguments or a brace expression to specify 
    multiple patterns. 

    To also follow directory symlinks, set 'follow' to 1 in the options hash 
    passed as the optional last argument. 
    Note that this changes the sort order - see below. 

    Traversal: 
    For recursive patterns, any given directory examined will have its matches 
    listed first, before descending depth-first into the subdirectories. 

    Hidden directories: 
    These are skipped by default, onless you set 'hiddendirs' to 1 in the 
    options hash passed as the optional last argument. 

    Sorting: 
    A given directory's matching items will always be sorted 
    case-insensitively, as with glob(), but sorting across directories 
    is only ensured, if the option to follow symlinks is NOT specified. 

    Duplicates: 
    Following symlinks only prevents cycles, so if a symlink and its target 
    they will both be reported. 
    (Under the hood, following symlinks activates the following 
    File::Find:find() options: `follow_fast`, with `follow_skip` set to 2.) 

    Since the default glob() function is at the heart of this function, its 
    rules - and quirks - apply here too: 
    - If literal components of your patterns contain pattern metacharacters, 
    - * ? { } [ ] - you must make sure that they're \-escaped to be treated 
    as literals; here's an expression that works on both Unix and Windows 
    systems: s/[][{}\-~*?]/\\$&/gr 
    - Unlike with glob(), however, whitespace in a pattern is considered part 
    of the pattern; to specify multiple patterns, use either a brace 
    expression (e.g., '{*.txt,*.md}'), or pass each pattern as a separate 
    argument. 
    - A pattern ending in '/' restricts matches to directories and symlinks 
    to directories, but, strangely, also includes symlinks to *files*. 
    - Hidden files and directories are NOT matched by default; use a separate 
    pattern starting with '.' to include them; e.g., globex '**/{.*,*}' 
    matches all files and directories, including hidden ones, in the 
    current dir.'s subtree. 
    Note: As with glob(), .* also matches '.' and '..' 
    - Tilde expansion is supported; escape as '\~' to treat a tilde as the 
    first char. as a literal. 
- A literal path (with no pattern chars. at all) is echoed as-is, 
    even if it doesn't refer to an existing filesystem item. 

COMPATIBILITY NOTES 
    Requires Perl v5.6.0+ 
    '/' must be used as the path separator on all platforms, even on Windows. 

EXAMPLES 
    # Find all *.txt files in the subtree of a dir stored in $mydir, including 
    # in hidden subdirs. 
    globex "$mydir/*.txt", { hiddendirs => 1 }; 

    # Find all *.txt and *.bak files in the current subtree. 
    globex '**/*.txt', '**/*.bak'; 

    # Ditto, though with different output ordering: 
    # Unlike above, where you get all *.txt files across all subdirs. first, 
    # then all *.bak files, here you'll get *.txt files, then *.bak files 
    # per subdirectory encountered. 
    globex '**/{*.txt,*.bak}'; 

    # Find all *.pm files anywhere in the subtrees of the directories in the 
    # module search path, @INC; follow symlinks. 
    # Note: The assumption is that no directory in @INC has embedded spaces 
    #  or contains pattern metacharacters. 
    globex '{' . (join ',', @INC) . '}/**/*.pm', { follow => 1 }; 
+2

Почему бы не бросить это на cpan? Я не могу найти аналогичный модуль – mikew

+0

Спасибо за предложение @mikew. Создание этого CPAN-готового требует довольно много работы и, следовательно, времени, которое у меня сейчас нет, но я буду иметь в виду. – mklement0