Как динамически создавать регулярные выражения Perl? - PullRequest
8 голосов
/ 22 мая 2009

У меня есть Perl-скрипт, который пересекает иерархию каталогов, используя File :: Next :: files. Он вернется только к файлам сценариев, которые заканчиваются на ".avi", ".flv", ".mp3", ".mp4" и ".wmv." Также будут пропущены следующие подкаталоги: «.svn» и все подкаталоги, оканчивающиеся на «.frames». Это указано в подпрограммах file_filter и descend_filter ниже.

my $iter = File::Next::files(
        { file_filter => \&file_filter, descend_filter => \&descend_filter },
        $directory );

sub file_filter { 
    # Called from File::Next:files.
    # Only select video files that end with the following extensions.
    /.(avi|flv|mp3|mp4|wmv)$/
}

sub descend_filter { 
    # Called from File::Next:files.
    # Skip subfolders that either end in ".frames" or are named the following:
    $File::Next::dir !~ /.frames$|^.svn$/
}

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

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

/.(avi|flv|mp3|mp4|wmv)$/

$File::Next::dir !~ /.frames$|^.svn$/

Ответы [ 6 ]

26 голосов
/ 22 мая 2009

Предполагая, что вы проанализировали файл конфигурации, чтобы получить список расширений и игнорируемых каталогов, вы можете построить регулярное выражение в виде строки, а затем использовать оператор qr для его компиляции в регулярное выражение:

my @extensions = qw(avi flv mp3 mp4 wmv);  # parsed from file
my $pattern    = '\.(' . join('|', @wanted) . ')$';
my $regex      = qr/$pattern/;

if ($file =~ $regex) {
    # do something
}

Компиляция не является строго необходимой; Вы можете использовать строковый шаблон напрямую:

if ($file =~ /$pattern/) {
    # do something
}

Каталоги немного сложнее, потому что у вас две разные ситуации: полные имена и суффиксы. Ваш файл конфигурации должен будет использовать разные ключи, чтобы было понятно, какой именно. например "dir_name" и "dir_suffix." Для полных имен я бы просто создал хеш:

%ignore = ('.svn' => 1);

Суффиксированные каталоги могут быть сделаны так же, как расширения файлов:

my $dir_pattern = '(?:' . join('|', map {quotemeta} @dir_suffix), ')$';
my $dir_regex   = qr/$dir_pattern/;

Вы можете даже встроить шаблоны в анонимные подпрограммы, чтобы избежать обращения к глобальным переменным:

my $file_filter    = sub { $_ =~ $regex };
my $descend_filter = sub {
    ! $ignore{$File::Next::dir} &&
    ! $File::Next::dir =~ $dir_regex;
};

my $iter = File::Next::files({
    file_filter    => $file_filter,
    descend_filter => $descend_filter,
}, $directory);
3 голосов
/ 22 мая 2009

Создайте его как обычную строку, а затем используйте интерполяцию в конце, чтобы превратить ее в скомпилированное регулярное выражение. Также будьте осторожны, вы не убегаете. или поместить его в класс символов, так что это означает любой символ (а не буквальный период).

#!/usr/bin/perl

use strict;
use warnings;

my (@ext, $dir, $dirp);
while (<DATA>) {
    next unless my ($key, $val) = /^ \s* (ext|dirp|dir) \s* = \s* (\S+)$/x;
    push @ext, $val if $key eq 'ext';
    $dir = $val     if $key eq 'dir';
    $dirp = $val    if $key eq 'dirp';
}

my $re = join "|", @ext;
$re = qr/[.]($re)$/;

print "$re\n";

while (<>) {
    print /$re/ ? "matched" : "didn't match", "\n";
}

__DATA__
ext = avi
ext = flv
ext = mp3
dir = .svn
dirp= .frames
3 голосов
/ 22 мая 2009

Допустим, вы используете Config :: General для своего конфигурационного файла и что он содержит следующие строки:

<MyApp>
    extensions    avi flv mp3 mp4 wmv
    unwanted      frames svn
</MyApp>

Затем вы можете использовать его следующим образом (подробнее см. В Config :: General):

my $conf = Config::General->new('/path/to/myapp.conf')->getall();
my $extension_string = $conf{'MyApp'}{'extensions'};

my @extensions = split m{ }, $extension_string;

# Some sanity checks maybe...

my $regex_builder = join '|', @extensions;

$regex_builder = '.(' . $regex_builder . ')$';

my $regex = qr/$regex_builder/;

if($file =~ m{$regex}) {
    # Do something.
}


my $uw_regex_builder = '.(' . join ('|', split (m{ }, $conf{'MyApp'}{'unwanted'})) . ')$';
my $unwanted_regex = qr/$uw_regex_builder/;

if(File::Next::dir !~ m{$unwanted_regex}) {
    # Do something. (Note that this does not enforce /^.svn$/. You
    # will need some kind of agreed syntax in your conf-file for that.
}

(Это полностью не проверено.)

1 голос
/ 25 мая 2009

Если вы хотите построить потенциально большое регулярное выражение и не хотите отлаживать скобки, используйте модуль Perl для его создания!

use strict;
use Regexp::Assemble;

my $re = Regexp::Assemble->new->add(qw(avi flv mp3 mp4 wmv));

...

if ($file =~ /$re/) {
    # a match!
}

print "$re\n"; # (?:(?:fl|wm)v|mp[34]|avi)
1 голос
/ 24 мая 2009

Это довольно просто с File :: Find :: Rule, просто случай создания списка до этого.

use strict;
use warnings;
use aliased 'File::Find::Rule';


# name can do both styles. 
my @ignoredDirs = (qr/^.svn/,  '*.frames' );
my @wantExt = qw( *.avi *.flv *.mp3 );

my $finder = Rule->or( 
    Rule->new->directory->name(@ignoredDirs)->prune->discard, 
    Rule->new->file->name(@wantExt)
);

$finder->start('./');

while( my $file = $finder->match() ){
    # Matching file.
}

Тогда это просто случай заполнения этих массивов. (Примечание: приведенный выше код также не проверен, но, скорее всего, будет работать). Я бы вообще использовал YAML для этого, это облегчает жизнь.

use strict;
use warnings;
use aliased 'File::Find::Rule';
use YAML::XS;

my $config = YAML::XS::Load(<<'EOF');
---
ignoredir:
- !!perl/regexp (?-xism:^.svn)
- '*.frames'
want:
- '*.avi'
- '*.flv'
- '*.mp3'
EOF

my $finder = Rule->or( 
    Rule->new->directory->name(@{ $config->{ignoredir} })->prune->discard, 
    Rule->new->file->name(@{ $config->{want} })
);

$finder->start('./');

while( my $file = $finder->match() ){
    # Matching file.
}

Примечание Использование удобного модуля aliased.pm, который импортирует для меня «File :: Find :: Rule» как «Rule».

  • File :: Find :: Rule - Альтернативный интерфейс для File :: Find
  • YAML :: XS - Сериализация Perl YAML с использованием XS и libyaml
  • aliased - Использовать более короткие версии имен классов.
0 голосов
/ 26 мая 2009

Хотя File :: Find :: Rule уже имеет способы справиться с этим, в подобных случаях вам не нужно регулярное выражение. Регулярное выражение здесь вам не очень дорого, потому что вы ищете фиксированную последовательность символов в конце каждого имени файла. Вы хотите знать, входит ли эта фиксированная последовательность в список последовательностей, которые вас интересуют. Сохраните все расширения в хэше и посмотрите на него:

my( $extension ) = $filename =~ m/\.([^.]+)$/;
if( exists $hash{$extension} ) { ... }

Вам не нужно создавать регулярное выражение, и вам не нужно проходить через несколько возможных чередований регулярных выражений, чтобы проверить каждое расширение, которое вы должны исследовать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...