Скрипт Bash для выбора диапазона, начинающегося с шаблона, охватывающего две строки и заканчивающегося пустой строкой.Sed? - PullRequest
3 голосов
/ 13 января 2012

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

2012-01-12 22:20:21,638 INFO  [Tracer]
  something.of.interest
  ...some number of additional lines...
<<a blank line>>
...other stuff...

Я хочу выделить только те блоки текста, где первая строка содержит [Tracer], а вторая строка содержит нечто.of.интерес , заканчивая пустой строкой неизвестное количество строк после вторая линия. Изменение формата файла не вариант.

Я могу использовать sed, чтобы выделить весь блок, выполнив что-то похожее на:

gsed -n '/^[0-9]\{4\}[^\[]*\[Tracer\]/,/^$/ p' /path/to/file/to/parse

Это выбирает весь блок, как и предполагалось, но совпадает с записями, где вторая строка не содержит что-то интересное.

Я могу использовать N, чтобы получить следующую строку, а затем сопоставить текущую и следующую строки, и я могу сделать эту работу, чтобы выбрать первые две строки, только когда я вижу и [Tracer], и что-то из интереса, разделенного \ п, однако я не могу понять, как захватить следующие строки, пока я не достигну новой строки. В псевдоеде я хочу сделать что-то вроде этого:

/look for Tracer line 1/{
N
/look for \n.*something.of.interest/
},
/look for blank line for end of range/
p

К сожалению, на самом деле это не работает, обычно я получаю сообщение «неизвестная команда».

Есть ли способ определить диапазон в sed, используя шаблоны, где начало и конец могут быть многострочными?

В конечном итоге решение должно работать на Solaris 5.10. GNU sed (gsed) доступен, как и awk, если это лучший выбор в этом случае.

Предложения очень ценятся.

UPDATE

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

1) Создайте файл something.awk, где первая строка - это регулярное выражение для соответствия строке 1:

/\[Tracer\]/ {
  l1=$0
  if (getline <= 0) {
      print "getline failed"
      exit 1
  }
  if (index($0, L2MARKER) > 0) {
      print l1
      print $0
      stop=0
      while(stop != 1) {
         if (getline <= 0) {
            print "getline failed :( ERRNO:" + ERRNO
            exit 1
         }
         print;
         if (length($0) == 0) {
             stop = 1
         }
      }
   }
}

2) Вызывать из оболочки, аналогичной awk -fthing.awk L2MARKER ='thing.of.интерес 'the.file.to.parse

Ответы [ 7 ]

2 голосов
/ 13 января 2012

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

Я придумал что-то, что могло бы сработать для вас, в sed:

/Tracer/ { N; /interesting/ { h; :a; n; H; /^$/! ba; g; p } }

Объяснение следует!

Имеется такой файл теста:

boring
boring
awesome [Tracer]
interesting
totally interesting
and awesome
still interesting
very interesting

back to boring
awesome [Tracer]
Nah just kidding
nope

darn

Приведенная выше команда расширяется до следующего с объяснением:

/Tracer/ {          # Looks for Tracer
    N               # Moves on to the next line
    /interesting/ { # Looks to see if "interesting" is in the next line
        h           # Put first two lines in hold space
        :a          # Label "a"
        n           # Move on to next line
        H           # Appends line to hold space
        /^$/! ba    # If not a blank line, branch back to "a"
        g           # Put the hold space into the pattern space
        p           # Print the pattern space
    }
}

И, должен сделать это так:

sed -n '/Tracer/ { h; n; /interesting/ { :a; H; n; /^$/! ba }; g; p }' file.txt
awesome [Tracer]
interesting
totally interesting
and awesome
still interesting
very interesting
[blank line]

Где [blank line] - буквальная пустая строка, но я не могу выразить это в блоке кода здесь. Очевидно, что вы можете достаточно легко изменить регулярные выражения для /Tracer/ и /interesting/, в зависимости от конкретного содержимого строк.

0 голосов
/ 13 января 2012

Я достигаю Perl для чтения текста в режиме "параграфа":

perl -00 -ne '
  @lines = split /\n/;
  print if $lines[0] =~ /Tracer/ and $lines[1] =~ /something.of.interest/
'
0 голосов
/ 13 января 2012

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

 sed -n '/Tracer/,/^$/{H;//{x;/something.of.interest/p}}' file

Объяснение:

  • -n отключить автоматическую печать, т. Е. Для печати используйте команду p или P.
  • Ограничить следующие команды диапазоном строк между /Tracer/ и /^$.
  • включительно, если строки соответствуют вышеуказанному, добавить новую строку, а затем пробел (PS).) в область удержания (HS). H
  • Если любое из регулярных выражений /Tracer/ или /^$/ соответствует, сделайте следующее //:
    • поменяйте местами PS для HSx
    • соответствует /something.of.interest/ и, если это так, распечатывает все в HS /.../p

PS всегда добавляется в HS междуИнтересующие линии в первую очередь (H).Первое совпадение // будет соответствовать первому адресу, т.е. /Tracer/, в этот момент HS становится PS, а PS становится HS.Ничто не соответствует, потому что /something.of.interest/ еще не прочитано. Поскольку совпадений нет, ничего не распечатывается, но теперь HS содержит первый адрес, к которому добавляются последующие строки до совпадения второго адреса.PS и HS меняются местами, и на этот раз /something.of.interest/ совпадает, и все строки между /Tracer/ и /^$/ выводятся на печать.

0 голосов
/ 13 января 2012

Sed имеет место для удержания, предназначенное для такого рода приложений:

sed -n '/^[0-9]\{4\}[^\[]*\[Tracer\]/,/^$/H;/^$/{x;/interesting/p;s/.*//g;x;}'
0 голосов
/ 13 января 2012

Если вы можете гарантировать, что сразу над строкой [Tracer] есть пустая строка,

awk 'BEGIN { RS = ""; FS = "\n" } $1 ~ "[Tracer]" && $2 ~ "something.of.interest"' input.file

Если нет, добавьте sed, чтобы убедиться в этом:

sed 's/\(.*\[Tracer\].*\)/\n\1/' test.in |awk \
    'BEGIN { RS=""; FS="\n" } $1 ~ "[Tracer]" && $2 ~ "something.of.interest"'
0 голосов
/ 13 января 2012

Вот еще одно awk решение:

awk '/\[Tracer\]/{
a=$0;getline;
if ($0~/something\.of\.interest/) {print a; print$0;getline} else next; 
while ($0!~/^$/) {print $0;getline}}' INPUT_FILE
0 голосов
/ 13 января 2012

другое решение awk

, если блок, который вы собираетесь извлечь, всегда находится вверху файла, и в вашем выводе будет только 1 или 0 блок:

awk '/\[Tracer\]/{print;f=1;next;} 
{if(NR==2){if(f && $0~/interest/){print;next;} else  exit;} 
if(f && $0) print; else exit;}' file 

если вы хотите поймать больше блоков:

awk '/\[Tracer\]/{h=$0;f=1;l=NR;next;} 
{if(NR==l+1){if(f && $0~/interest/){print h;print;} else {f=0;h=""; }next;} 
if(f) if($0)print;else f=0; }' file

тест на обоих выше

    #input file, in this case, 1st and 3rd blocks should be 
in your output (for solution 2)
kent$  cat file
2012-01-12 22:20:21,638 INFO  [Tracer]
  interesting
  ...some number of additional lines...
  xxx
  yyy
  zzz

...other stuff...
2012-01-12 22:20:21,638 INFO  [Tracer]
  NOT-wanted-NOT
  ...some number of additional lines...
  xxx####
  yyy####
  zzz####

...other ####  stuff...
2012-01-12 22:20:21,638 INFO  [Tracer]
  interest
  ...some number of additional lines...
  xxxWANTWANTWANT
  yyy.WANTWANTWANT
  zzzoWANTWANTWANT

  blahblah

#solution one:
kent$   awk '/\[Tracer\]/{print;f=1;next;} 
    {if(NR==2){if(f && $0~/interest/){print;next;} else  exit;} 
    if(f && $0) print; else exit;}' file 
2012-01-12 22:20:21,638 INFO  [Tracer]
  interesting
  ...some number of additional lines...
  xxx
  yyy
  zzz

#solution two:
kent$  awk '/\[Tracer\]/{h=$0;f=1;l=NR;next;} 
{if(NR==l+1){if(f && $0~/interest/){print h;print;} else {f=0;h=""; }next;} 
if(f) if($0)print;else f=0; }' file 
2012-01-12 22:20:21,638 INFO  [Tracer]
  interesting
  ...some number of additional lines...
  xxx
  yyy
  zzz
2012-01-12 22:20:21,638 INFO  [Tracer]
  interest
  ...some number of additional lines...
  xxxWANTWANTWANT
  yyy.WANTWANTWANT
  zzzoWANTWANTWANT

надеюсь, это поможет.

...