Я ищу линии между двумя совпадающими шаблонами. Если отсутствует начальный или конечный шаблон, линии не должны печататься.
Правильный ввод:
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