Многострочный perl regex заменяет большой файл без ошибок - PullRequest
2 голосов
/ 26 сентября 2019

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

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

В меньшем файле следующая строка делаетчто мне нужно: perl -0777 -i -pe 's/,\s+\)/\n\)/g' inputfile.txt

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

Когда я пыталсязапустите его в моем производственном файле. Через пару минут я только что получил сообщение «Killed» в терминале, и содержимое файла было полностью стерто.Я наблюдал за использованием памяти во время этого и, как и ожидалось, он работал на 100% и широко использовал пространство подкачки.

Есть ли способ заставить эту команду perl запускаться вместо двух строк одновременно, или альтернативныйКоманда bash, которая может достичь того же результата?

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

Ответы [ 5 ]

4 голосов
/ 26 сентября 2019

Довольно прямолинейная логика:

  • печатать строку, если она не заканчивается запятой (необходимо проверить следующую строку, возможно, удалить ее)

  • вывести предыдущую строку ($p), если у нее была запятая, без нее, если текущая строка начинается с )

perl -ne'
    if ($p =~ /,$/) { $p =~ s/,$// if /^\s*\)/; print $p }; 
    print unless /,$/; 
    $p = $_
' file

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

Протестировано с file

hello
here's a comma,
which was fine
(but here's another,
) which has to go,
and that was another good one.
end

Приведенное выше не может распечатать последнюю строку, если она заканчивается запятой.Одним из исправлений для этого является проверка нашего буфера (предыдущая строка $p) в END блоке , поэтому в конце добавляется

END { print $p if $p =~ /,$/}

Это довольно обычный способпроверить наличие конечных буферов или условий в однострочниках -n / -p.

Еще одно исправление, менее эффективное, но, возможно, с более чистым кодом, состоит в замене оператора

print unless /,$/;

с

print if (not /,$/ or eof);

Это запускает проверку eof для каждой строки файла, в то время как END запускает один раз .

2 голосов
/ 26 сентября 2019

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

perl -ne'
   $_ = $buf . $_;
   s/^,(?=\n\))//;
   $buf = s/(,\n)\z// ? $1 : "";
   print;
   END { print $buf; }
'

Быстрее:

perl -ne'
   print /^\)/ ? "\n" : ",\n" if $f;
   $f = s/,\n//;
   print;
   END { print ",\n" if $f; }
'

Указание файла для обработки вPerl однострочный

2 голосов
/ 26 сентября 2019

Если использовать \n перевод строки в качестве разделителя записей неудобно, используйте что-то еще.В этом случае вас особенно интересует последовательность ,\n), и мы можем позволить Perl найти это для нас, когда мы читаем файл:

perl -pe 'BEGIN{ $/ = ",\n)" } s/,\n\)/\n)/' input.txt >output.txt

Эта часть: $/ = ",\n)" сообщает Perl, что вместоперебирая строки файла, он должен перебирать записи, оканчивающиеся последовательностью ,\n).Это помогает нам гарантировать, что каждый блок будет иметь не более одной такой последовательности, но, что более важно, эта последовательность не будет охватывать фрагменты (или записи, или чтения из файла).Каждое чтение фрагмента будет либо заканчиваться ,\n), либо, в случае окончательной записи, может заканчиваться отсутствием терминатора записи (по нашему определению терминатора).

Далее мы просто используем подстановку для удаления этой запятой внаша ,\n) последовательность-разделитель записей.

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

Как уже упоминалось в комментариях, это решение наиболее полезно, только если промежуток между ,\n) последовательностями не превышает объем памяти, который вы готовы использовать для решения проблемы.Скорее всего, сами новые строки встречаются в файле чаще, чем ,\n) последовательностей, и поэтому это будет читаться большими кусками.Вы знаете, что ваш набор данных лучше, чем мы, и поэтому можете лучше судить о том, перевешивает ли простота этого решения объем его памяти.

1 голос
/ 26 сентября 2019

Это можно сделать проще всего с помощью awk.

awk 'BEGIN{RS=".\n."; ORS=""} {gsub(",\n)", "\n)", RT); print $0 RT}'

Объяснение:

awk, в отличие от Perl, допускает регулярное выражение в качестве разделителя записей, здесь .\n.который "захватывает" два символа, окружающие каждую новую строку.

Установка пустого значения ORS не позволяет print выводить дополнительные символы новой строки.Все новые строки записываются в RS / RT.

RT представляет фактический текст, соответствующий регулярному выражению RS.

gsub удаляет любую желаемую запятую изRT если присутствует.

Предостережение: вам понадобится GNU awk (gawk), чтобы это работало.Похоже, что только для POSIX awk будет отсутствовать регулярное выражение RS с переменной RT, согласно справочной странице gawk.

Примечание: gsub на самом деле не нужно, sub достаточно хорош и, вероятно, должен был использоваться выше.

0 голосов
/ 27 сентября 2019

Это может сработать для вас (GNU sed):

sed 'N;s/,\n)/\n)/;P;D' file

Держите движущееся окно из двух строк по всему файлу, и если первое заканчивается на ,, а второе начинается на ), удалите ,.

Если есть пробел и его нужно сохранить, используйте:

sed 'N;s/,\(\s*\n\s*)\)/\1/;P;D' file
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...