Печатать строки между начальным и конечным шаблоном, но если конечный шаблон не существует, не печатать

Печатать строки между начальным и конечным шаблоном, но если конечный шаблон не существует, не печатать

Я ищу линии между двумя совпадающими шаблонами. Если отсутствует начальный или конечный шаблон, линии не должны печататься.

Правильный ввод:

a
***** BEGIN *****
BASH is awesome
BASH is awesome
***** END *****
b

Выход будет

***** BEGIN *****
BASH is awesome
BASH is awesome
***** END *****

Теперь предположим, что во входных данных отсутствует шаблон END.

a
***** BEGIN *****
BASH is awesome
BASH is awesome
b

Линии не должны печататься.

Я попробовал с sed:

sed -n '/BEGIN/,/END/p' input

Если шаблон END отсутствует, выводятся все данные до последней строки.

Как это решить?

решение1

Это можно сделать следующим образом:

$ sed -e '
    /BEGIN/,/END/!d
    H;/BEGIN/h;/END/!d;g
' inp

Как это работает, для диапазона начала/конца строк он сохраняет их в пространстве удержания. Затем удаляет, пока не встретится строка КОНЕЦ. В этот момент мы вызываем то, что находится в удержании. OTW, мы ничего не получаем. HTH.

решение2

cat input |
sed '/\*\*\*\*\* BEGIN \*\*\*\*\*/,/\*\*\*\*\* END *\*\*\*\*/ p;d' | 
tac |
sed '/\*\*\*\*\* END \*\*\*\*\*/,/\*\*\*\*\* BEGIN *\*\*\*\*/ p;d' |
tac

Это работает путем tacперестановки строк, что sedпозволяет найти оба разделителя в обоих порядках.

решение3

С pcregrep:

pcregrep -M '(?s)BEGIN.*?END'

Это также работает, если BEGIN и END находятся на одной строке, но не в таких случаях, как:

BEGIN 1 END foo BEGIN 2
END

Где pcregrepловит первое BEGIN 1 END, но не второе.

Чтобы справиться с ними, с помощью awk, вы можете сделать следующее:

awk '
  !inside {
    if (match($0, /^.*BEGIN/)) {
      inside = 1
      remembered = substr($0, 1, RLENGTH)
      $0 = substr($0, RLENGTH + 1)
    } else next
  }
  {
    if (match($0, /^.*END/)) {
      print remembered $0
      if (substr($0, RLENGTH+1) ~ /BEGIN/)
        remembered = ""
      else
        inside = 0
    } else
      remembered = remembered $0 ORS
  }'

На входе типа:

a
BEGIN blah END BEGIN 1
2
END
b
BEGIN foo END
c
BEGIN
bar
END BEGIN
baz END
d
BEGIN
xxx

Это дает:

BEGIN blah END BEGIN 1
2
END
BEGIN foo END
BEGIN
bar
END BEGIN
baz END

Оба должны хранить все от BEGIN до следующего END в памяти. Так что если у вас есть огромный файл, первая строка которого содержит BEGIN, но без END, весь файл будет храниться в памяти впустую.

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

решение4

Подход GNU awk. Результат достигается путем установки определенных переменных при нахождении начального заголовка. Некоторые переменные могут быть сокращены для удобства

$ awk '/BEGIN/{a[i++]=$0;flag=1;next};flag==1{a[i++]=$0;if($0~/END/){print_array=1; nextfile;} }; END{if(print_array) for(j=0;j<=i;j++)print a[j]}' input.txt
***** BEGIN *****
BASH is awesome
BASH is awesome
***** END *****

При отсутствии флага END результат, как и ожидалось, равен нулю:

$ awk '/BEGIN/{a[i++]=$0;flag=1;next};flag==1{a[i++]=$0;if($0~/END/){print_array=1; nextfile;} }; END{if(print_array) for(j=0;j<=i;j++)print a[j]}' input2.txt

Связанный контент