Как в Perl выделить строки между двумя разделителями строк?

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

Файл имеет такую ​​структуру:

... 
... some garbage 
... 
... garbage START
what i want is 
on different
lines 
END 
... 
... more garbage ...
next one START 
more stuff I want, again
spread 
through 
multiple lines 
END 
...
more garbage

Итак, я ищу способ извлечь строки между каждой строкой-разделителем START и END. Как я могу это сделать?

Пока что я нашел только несколько примеров того, как напечатать строку со строкой START или другие элементы документации, которые в некоторой степени связаны с тем, что я ищу.


person jbatista    schedule 31.07.2009    source источник
comment
Используйте глобальное совпадение / g, а не позволяйте ему останавливаться на терминаторе строки.   -  person Lazarus    schedule 31.07.2009
comment
ты имел ввиду / s? AFAIK / g - это множественное совпадение.   -  person Steve Schnepp    schedule 31.07.2009
comment
Это повторяющийся вопрос. См. .... stackoverflow.com/questions/296366/   -  person draegtun    schedule 31.07.2009
comment


Ответы (6)


Вам нужен оператор триггера (также известный как оператор диапазона) ..

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

while (<>) {
  if (/START/../END/) {
    next if /START/ || /END/;
    print;
  }
}

Замените вызов print тем, что вы действительно хотите сделать (например, вставить строку в массив, отредактировать ее, отформатировать и т. Д.). Я next прохожу мимо строк, которые на самом деле имеют START или END, но вам может не понравиться такое поведение. См. эту статью для обсуждения этого оператора и другие полезные специальные переменные Perl.

person Telemachus    schedule 31.07.2009
comment
Работает на меня!! Так как я хочу исключить строки с разделителями, я могу передать вывод, например, через grep -v. Кстати, в первой строке после START, как я могу удалить первый символ в строке? - person jbatista; 31.07.2009
comment
Однострочная версия: perl -ne 'print if /START/../END/' - person William Pursell; 31.07.2009
comment
Уильям, это напечатает строки с START и END. Если они вам не нужны, вот единственная инструкция Telemachus: perl -ne 'if (/START/../END/) {print except / START / or / END /}' - person glenn jackman; 31.07.2009
comment
@Telemachus - Как мне заставить это работать с переменной, а не читать из файла. Скажите, у меня есть $variable = "dont want this part START i want this part instead END";? Поскольку у меня возникают проблемы с получением того же эффекта, когда это переменная, а не файл, ваша помощь очень ценится, спасибо - person yonetpkbji; 03.05.2013
comment
@ perl-user. Если строка длиннее (и особенно если она разделена символами новой строки или чем-то очень обычным), вы можете использовать open и обрабатывать строковую переменную как дескриптор файла. Но если строка действительно выглядит так, как у вас здесь, она того не стоит. Вы можете просто использовать замену, чтобы удалить ненужные части: например, s/^.*START //, а затем s/ END$//. Если посложнее, я бы открыл новый вопрос. - person Telemachus; 04.05.2013

Из ответа perlfaq6 на Как выделить линии между двумя шаблонами, которые сами находятся на разных линиях?


Вы можете использовать несколько экзотический оператор Perl .. (задокументированный в perlop):

perl -ne 'print if /START/ .. /END/' file1 file2 ...

Если вам нужен текст, а не строки, вы должны использовать

perl -0777 -ne 'print "$1\n" while /START(.*?)END/gs' file1 file2 ...

Но если вам нужны вложенные вхождения от START до END, вы столкнетесь с проблемой, описанной в вопросе в этом разделе о сопоставлении сбалансированного текста.

Вот еще один пример использования ..:

while (<>) {
    $in_header =   1  .. /^$/;
    $in_body   = /^$/ .. eof;
# now choose between them
} continue {
    $. = 0 if eof;  # fix $.
}
person brian d foy    schedule 31.07.2009

Как я могу захватить несколько строк после соответствующая строка в Perl?

Как это? В этом случае строка END - это $ ^, вы можете изменить ее на свою строку END.

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

person Dirk    schedule 31.07.2009

while (<>) {
    chomp;      # strip record separator
    if(/END/) { $f=0;}
    if (/START/) {
        s/.*START//g;
        $f=1;
    }
    print $_ ."\n" if $f;
}

попробуйте написать код в следующий раз

person ghostdog74    schedule 31.07.2009
comment
Я понимаю, и я бы написал код, если бы я уже начал изучать Perl. Мне пока удалось обойтись awk и sed. Но в любом случае спасибо за совет. - person jbatista; 31.07.2009

После ответа Телемаха все начало выливаться наружу. В конце концов, это работает как решение, которое я ищу.

  1. Я пытаюсь выделить строки, разделенные двумя строками (одна - со строкой, заканчивающейся на «CINFILE =»; другая, со строкой, содержащей один «#»), в отдельные строки, за исключением строк-разделителей. Я могу это сделать с помощью решения Телемаха.
  2. В первой строке есть пробел, который я хочу удалить. Я тоже это включаю.
  3. Я также пытаюсь извлечь каждый набор строк в отдельные файлы.

У меня это работает, хотя код можно отнести к категории уродливых; это потому, что в настоящее время я практически новичок в Perl. Во всяком случае, здесь идет:

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

my $start='CINFILE=$';
my $stop='^#$';
my $filename;
my $output;
my $counter=1;
my $found=0;

while (<>) {
  if (/$start/../$stop/) {
    $filename=sprintf("boletim_%06d.log",$counter);
    open($output,'>>'.$filename) or die $!;
    next if /$start/ || /$stop/;
    if($found == 0) { print $output (split(/ /))[1]; }
    else { print $output $_; }
    $found=1;
  } else { if($found == 1) { close($output); $counter++; $found=0; } }
}

Я надеюсь, что это принесет пользу и другим. Ваше здоровье.

person jbatista    schedule 31.07.2009

Неплохо для "виртуального новичка". Одна вещь, которую вы могли бы сделать, - это поместить «$ found = 1» внутри блока «if ($ found == 0)», чтобы вы не выполняли это присвоение каждый раз между $ start и $ stop.

Еще одна неприятная вещь, на мой взгляд, заключается в том, что вы открываете один и тот же обработчик файлов каждый раз, когда вводите блок $ start / $ stop.

Это показывает способ обойти это:

#!/usr/bin/perl

use strict;
use warnings;

my $start='CINFILE=$';
my $stop='^#$';
my $filename;
my $output;
my $counter=1;
my $found=0;

while (<>) {

    # Find block of lines to extract                                                           
    if( /$start/../$stop/ ) {

        # Start of block                                                                       
        if( /$start/ ) {
            $filename=sprintf("boletim_%06d.log",$counter);
            open($output,'>>'.$filename) or die $!;
        }
        # End of block                                                                         
        elsif ( /$end/ ) {
            close($output);
            $counter++;
            $found = 0;
        }
        # Middle of block                                                                      
        else{
            if($found == 0) {
                print $output (split(/ /))[1];
                $found=1;
            }
            else {
                print $output $_;
            }
        }

    }
    # Find block of lines to extract                                                           

}
person dala    schedule 05.08.2009
comment
Спасибо. Теперь я чувствую, что мне следует потратить ^ H ^ H ^ H ^ H ^ Huse некоторое время на то, чтобы как следует изучить Perl. Мой опыт работы с C, немного C ++ и немного Fortran, так что это кажется мне знакомым. - person jbatista; 07.08.2009
comment
Между прочим, я признаю, что я был небрежно открывал много файлов, моей главной заботой в то время было получить что-то, что действительно работало, даже если не слишком хорошо. - person jbatista; 07.08.2009