Как регулярное выражение поиска / замены с File :: Map в большой текстовый файл с, чтобы избежать "Out of Memory" -Error? - PullRequest
1 голос
/ 11 июня 2011

ОБНОВЛЕНИЕ 2: решено.См. Ниже.

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

BEGIN {undef $/; };
open $in,  '<',  "orig.txt"      or die "Can't read old file: $!"; 
open $out, '>',  "mod.txt"  or die "Can't write new file: $!";
while( <$in> )  
{
$C=s/foo/bar/gm;
print "$C matches replaced.\n"
etc...
print $out $_;
}
close $out;

Это довольно быстро, но через некоторое время я всегда получаю «Out of Memory» -Error из-за нехватки RAM/ Swap-Space (у меня Win XP с 2 ГБ оперативной памяти и 1,5 ГБ Swap-файлом).После небольшого осмотра того, как работать с большими файлами, File::Map показался мне хорошим способом избежать этой проблемы.У меня проблемы с его реализацией.Это то, что у меня есть сейчас:

#!perl -w
use strict; 
use warnings;
use File::Map qw(map_file);

my $out = 'output.txt';
map_file my $map, 'input.txt', '<';
$map =~ s/foo/bar/gm;

print $out $map;

Однако я получаю следующую ошибку: Modification of a read-only value attempted at gott.pl line 8.

Кроме того, я прочитал на странице справки File::Map, что не в Unixсистемы мне нужно использовать binmode.Как мне это сделать?

По сути, я хочу "загрузить" файл через File :: Map, а затем запустить код, подобный следующему:

$C=s/foo/bar/gm;
print "$C matches found and replaced.\n"

$C=s/goo/far/gm;
print "$C matches found and replaced.\n"
while(m/complex_condition/gm)
{ 
$C=s/complex/regex/gm;
$run_counter++;
}
print "$C matches replaced. Script looped $run_counter times.\n";
etc...

Я надеюсьчто я не упустил из виду что-то слишком очевидное, но пример, приведенный на странице справки File::Map, показывает только, как читать из сопоставленного файла, правильно?

РЕДАКТИРОВАТЬ:

Чтобы лучше проиллюстрировать то, что я в настоящее время не могу выполнить из-за нехватки памяти, приведу пример:

On http://pastebin.com/6Ehnx6xA - пример одной из наших экспортированных библиотек.записи (txt-формат).Меня интересует часть +Deskriptoren:, начинающаяся со строки 46. Это тематические классификаторы, которые организованы в виде древовидной иерархии.

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

+Deskriptoren
-foo
-Cultural Revolution
-bar

в

+Deskriptoren
-foo
-History
-Modern History
-PRC
-Cultural Revolution
-bar

Используемое в настоящее время регулярное выражение использует Lookbehind и Lookahead, чтобы избежать дублирования дубликатов, и, следовательно, немного сложнее, чем s/foo/bar/g;:

s/(?<=\+Deskriptoren:\n)((?:-(?!\QParent-Node\E).+\n)*)-(Child-Node_1|Child-Node_2|...|Child-Node_11)\n((?:-(?!Parent-Node).+\n)*)/${1}-Parent-Node\n-${2}\n${3}/g;

Но это работает!До тех пор, пока в Perl не кончится память, это ...: /

Так что, по сути, мне нужен способ выполнять манипуляции с большим файлом (80 МБ) в несколько строк.Время обработки не является проблемой.Вот почему я подумал о File :: Map.Другим вариантом может быть обработка файла в несколько этапов с помощью связанных perl-скриптов, вызывающих друг друга, а затем завершающихся, но я бы хотел сохранить его как можно больше в одном месте.

ОБНОВЛЕНИЕ 2:

Мне удалось заставить его работать с кодом Швельма ниже.Мой сценарий теперь вызывает следующую подпрограмму, которая вызывает две вложенные подпрограммы.Пример кода: http://pastebin.com/SQd2f8ZZ

Все еще не совсем удовлетворен тем, что я не могу заставить File::Map работать.Ну да ладно ... Я полагаю, что линейный подход в любом случае более эффективен.

Спасибо всем!

Ответы [ 3 ]

7 голосов
/ 11 июня 2011

Когда вы устанавливаете $/ (разделитель входных записей) на неопределенное значение, вы «краете» файл - читая все содержимое файла одновременно (это обсуждается в perlvar , для пример). Отсюда проблема нехватки памяти.

Вместо этого обрабатывайте его по одной строке за раз, если можете:

while (my $line = <$in>){
    # Do stuff.
}

В ситуациях, когда файл достаточно мал, и вы делаете его неэффективным, цикл while не требуется. Первое чтение получает все:

{
    local $/ = undef;
    my $file_content = <>;
    # Do stuff with the complete file.
}

Обновление

После просмотра вашего огромного регулярного выражения я призываю вас пересмотреть свою стратегию. Решите это как проблему разбора : обрабатывайте файл по одной строке за раз, сохраняя информацию о состоянии анализатора по мере необходимости. Этот подход позволяет вам работать с информацией, используя простые, понятные (даже тестируемые) шаги.

Ваша текущая стратегия - можно назвать это бред и удар с массивной стратегией регулярных выражений - ее трудно понять и поддерживать (через 3 месяца ваше регулярное выражение сразу же станет для вас смыслом?), Трудно для тестирования и отладки, а также трудно настроить, если вы обнаружите непредвиденные отклонения от вашего первоначального понимания данных. Кроме того, как вы обнаружили, стратегия уязвима к ограничениям памяти (из-за необходимости извлекать файл из файла).

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

3 голосов
/ 12 июня 2011

Некоторый простой анализ может разбить файл на управляемые куски. Алгоритм:

1. Read until you see `+Deskriptoren:`
2. Read everything after that until the next `+Foo:` line
3. Munge that bit.
4. Goto 1.

Вот эскиз кода:

use strict;
use warnings;
use autodie;

open my $in,  $input_file;
open my $out, $output_file;

while(my $line = <$in>) {
    # Print out everything you don't modify
    # this includes the +Deskriptoren line.
    print $out $line;

    # When the start of a description block is seen, slurp in up to
    # the next section.
    if( $line =~ m{^ \Q Deskriptoren: }x ) {
        my($section, $next_line) = _read_to_next_section($in);

        # Print the modified description
        print $out _munge_description($section);

        # And the following header line.
        print $out $next_line;
    }
}

sub _read_to_next_section {
    my $in = shift;

    my $section = '';
    my $line;
    while( $line = <$in> ) {
        last if $line =~ /^ \+ /x;
        $section .= $line;
    }

    # When reading the last section, there might not be a next line
    # resulting in $line begin undefined.
    $line = '' if !defined $line;
    return($section, $line);
}

# Note, the +Deskriptoren line is not on $description
sub _munge_description {
    my $description = shift;

    ...whatever you want to do to the description...

    return $description;
}

Я не проверял это, но что-то подобное должно вам помочь. Он имеет преимущество перед обработкой всего файла в виде строки (File :: Map или иным образом), что вы можете работать с каждым разделом индивидуально, а не пытаться охватить каждую базу одним регулярным выражением. Это также позволит вам разработать более сложный синтаксический анализатор, чтобы иметь дело с такими вещами, как комментарии и строки, которые могут испортить простой синтаксический анализ, описанный выше, и это будет огромной болью для адаптации огромного регулярного выражения.

1 голос
/ 11 июня 2011

Вы используете режим <, который доступен только для чтения. Если вы хотите изменить содержимое, вам нужен доступ для чтения и записи, поэтому вы должны использовать +<.

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

Я также заметил, что у вас есть входной файл и выходной файл. Если вы используете File :: Map, вы меняете файл на месте ... то есть вы не можете открыть файл для чтения и изменить содержимое другого файла. Вам нужно будет скопировать файл, а затем изменить копию. Я сделал это ниже.

use strict;
use warnings;

use File::Map qw(map_file);
use File::Copy;

copy("input.txt", "output.txt") or die "Cannot copy input.txt to output.txt: $!\n";

open my $fh, '+<', "output.txt"
    or die "Cannot open output.txt in r/w mode: $!\n";

binmode($fh);

map_handle my $contents, $fh, '+<';

my $n_changes = ( $contents =~ s/from/to/gm );

unmap($contents);
close($fh);

Документация для File::Map не очень хороша в отношении того, как сообщается об ошибках, но из источника это выглядит так, как если бы значение 1011 * было неопределенным, было бы хорошим предположением.

...