Извлечение всего между первым и последним вхождением одного и того же шаблона в одну итерацию - PullRequest
0 голосов
/ 06 июня 2018

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

С учетом шаблона CAPTURE и ввода

1:.........
...........
100:CAPTURE
...........
150:CAPTURE
...........
200:CAPTURE
...........
1000:......

Печать:

100:CAPTURE
...........
150:CAPTURE
...........
200:CAPTURE

Можно ли это сделать с помощью регулярного выражения?

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

Ответы [ 7 ]

0 голосов
/ 07 июня 2018

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

sed '/CAPTURE/!d;:a;n;:b;//ba;$d;N;bb' file

Удалять все строки до первой, содержащей требуемую строку.Выведите строку, содержащую требуемую строку.Замените пространство шаблона следующей строкой.Если эта строка содержит требуемую строку, повторите два последних предыдущих предложения.Если это последняя строка файла, удалите пространство шаблона.В противном случае добавьте следующую строку и повторите последние три предыдущих предложения.

Изучив тестовые файлы, используемые для теста haukex, может показаться, что sed не является инструментом для извлечения этого файла.Использование сочетаний csplit, grep и sed представляет собой достаточно быстрое решение следующим образом:

lines=$(grep -nTA1 --no-group-separator CAPTURE oldFile | 
        sed '1s/\t.*//;1h;$!d;s/\t.*//;H;x;s/\n/ /')
csplit -s oldFile $lines && rm xx0{0,2} && mv xx01 newFile

Разделить исходный файл на три файла.Файл, предшествующий первому вхождению CAPTURE, файл от первого CAPTURE до последнего CAPTURE и файл, содержащий остаток.Первый и третий файлы удаляются, а второй файл переименовывается.

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

При запуске с тестовыми файлами (как указано выше) время ожидания составляет около 10 секунд.

0 голосов
/ 09 июня 2018

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

[Timestamp] (AppName) {EventId} [INFO]: Log text...
[Timestamp] (AppName) {EventId} [EXCEPTION]: Log text...
                    at com.application.class(Class.java:154)
                    caused by......
[Timestamp] (AppName) {EventId} [LogLevel]: Log text...

Учитывая EventId, мне нужно было извлечь из этих файлов все строки, соответствующие событию.Проблема стала неразрешимой с тривиальным grep для EventId только из-за того, что строки исключений могли иметь произвольную длину и не содержать EventId.

К сожалению, я забыл рассмотреть крайний случай, когдапоследняя строка журнала для EventId может быть исключением, и ответы, опубликованные здесь, не будут печатать строки трассировки стека.Однако было несложно изменить решение haukex, чтобы охватить и эти случаи:

#!/usr/bin/env perl
use warnings;
use strict;

my $first=1;
my @buf;
while ( my $line = <> ) {
    push @buf, $line unless $first;
    if ( $line=~/EventId/ or ($first==0 and $line!~/\(AppName\)/)) {
        if ($first) {
            @buf = ($line);
            $first = 0;
        }
        print @buf;
        @buf = ();
    }
    else {
        $first = 1;
    }
}

Мне все еще интересно, могут ли быть изменены более быстрые решения (в основном решение sed Уолтера или perl-решение haukex в памяти)сделать то же самое.

0 голосов
/ 07 июня 2018

Найдите первую CAPTURE и посмотрите на последнюю.

echo "/CAPTURE/,?CAPTURE? p" | ed -s <(gunzip -c inputfile.gz)

EDIT: ответ на комментарий и второе (лучшее?) Решение.

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

# With newline
printf "1,$ p\n" | ed -s <(printf "%s\n" test)
# Without newline
printf "1,$ p\n" | ed -s <(printf "%s" test)
# message removed
printf "1,$ p\n" | ed -s <(printf "%s" test) 2> /dev/null

Я не знаю осложнений с памятью, которые это даст для большого файла, но вы бы предпочли потоковое решение.
Вы можете использовать sed для следующего подхода.Продолжайте читать строки, пока не найдете первое совпадение.В течение этого времени запоминайте только последнюю прочитанную строку (поместив ее в область Hold).
Теперь измените свою тактику.
Добавьте каждую строку в область Hold.Вы не знаете, когда делать сброс до следующего матча.
Когда у вас будет следующий матч, вспомните область Hold и напечатайте ее.
Мне потребовался некоторый твик для предотвращения печати второго матча дважды.Я решил эту проблему, прочитав следующую строку и заменив область HOLD этой строкой.
Общее решение:

gunzip -c inputfile.gz | sed -n '1,/CAPTURE/{h;n};H;/CAPTURE/{x;p;n;h};'

Если вам не нравится удерживающее пространство sed, вы можете реализоватьтакой же подход с awk:

gunzip -c inputfile.gz | 
   awk '/CAPTURE/{capt=1} capt==1{a[i++]=$0} /CAPTURE/{for(j=0;j<i;j++) print a[j]; i=0}'
0 голосов
/ 06 июня 2018

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

#!/usr/bin/perl
{
  local $/ = undef;
  open FILE, $ARGV[0] or die "Couldn't open file: $!";
  binmode FILE;
  $string = <FILE>;
  close FILE;
}

print $1 if $string =~ /([^\n]+(CAPTURE).*\2.*?)\n/s;

Или с одним вкладышем:

cat file.tmp | perl -ne '$/=undef; print $1 if <STDIN> =~ /([^\n]+(CAPTURE).*\2.*?)\n/s'

результат:

100:CAPTURE
...........
150:CAPTURE
...........
200:CAPTURE
0 голосов
/ 06 июня 2018

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

#!/usr/bin/env perl
use warnings;
use strict;

my $first=1;
my @buf;
while ( my $line = <> ) {
    push @buf, $line unless $first;
    if ( $line=~/CAPTURE/ ) {
        if ($first) {
            @buf = ($line);
            $first = 0;
        }
        print @buf;
        @buf = ();
    }
}

Подайте входные данные в эту программу через zcat file.gz | perl script.pl.

Что, разумеется, можно застрять в однострочнике, если это необходимо ...

zcat file.gz | perl -ne '$x&&push@b,$_;if(/CAPTURE/){$x||=@b=$_;print@b;@b=()}'

Можно ли это сделать с помощью регулярного выражения?

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

zcat file.gz | perl -0777ne '/((^.*CAPTURE.*$)(?s:.*)(?2)(?:\z|\n))/m and print $1'
0 голосов
/ 06 июня 2018

Я бы написал

gunzip -c file.gz | sed -n '/CAPTURE/,$p' | tac | sed -n '/CAPTURE/,$p' | tac
0 голосов
/ 06 июня 2018

Я не думаю, что регулярное выражение будет быстрее, чем двойное сканирование ...

Вот решение awk (двойное сканирование)

$ awk '/pattern/ && NR==FNR {a[++f]=NR; next} a[1]<=FNR && FNR<=a[f]' file{,} 

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

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