Estou procurando encontrar as linhas entre dois padrões correspondentes. Se faltar algum padrão inicial ou final, as linhas não deverão ser impressas.
Entrada correta:
a
***** BEGIN *****
BASH is awesome
BASH is awesome
***** END *****
b
A saída será
***** BEGIN *****
BASH is awesome
BASH is awesome
***** END *****
Agora suponha que o padrão END esteja faltando na entrada
a
***** BEGIN *****
BASH is awesome
BASH is awesome
b
As linhas não devem ser impressas.
Eu tentei com sed:
sed -n '/BEGIN/,/END/p' input
Ele imprime todos os dados até a última linha se o padrão END estiver faltando.
Como resolver isso?
Responder1
Você pode fazer isso da seguinte maneira:
$ sed -e '
/BEGIN/,/END/!d
H;/BEGIN/h;/END/!d;g
' inp
Como funciona é que, para o intervalo inicial/final das linhas, ele as armazena no espaço de espera. Em seguida, exclui até encontrar a linha END. Nesse ponto, lembramos o que está em espera. OTW, não conseguimos nada. HTH.
Responder2
cat input |
sed '/\*\*\*\*\* BEGIN \*\*\*\*\*/,/\*\*\*\*\* END *\*\*\*\*/ p;d' |
tac |
sed '/\*\*\*\*\* END \*\*\*\*\*/,/\*\*\*\*\* BEGIN *\*\*\*\*/ p;d' |
tac
Funciona invertendo tac
as linhas para sed
encontrar os dois delimitadores em ambas as ordens.
Responder3
Com pcregrep
:
pcregrep -M '(?s)BEGIN.*?END'
Isso também funciona se BEGIN e END estiverem na mesma linha, mas não em casos como:
BEGIN 1 END foo BEGIN 2
END
Onde pcregrep
pega o primeiro BEGIN 1 END
, mas não o segundo.
Para lidar com isso, com awk
você poderia fazer:
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
}'
Em uma entrada como:
a
BEGIN blah END BEGIN 1
2
END
b
BEGIN foo END
c
BEGIN
bar
END BEGIN
baz END
d
BEGIN
xxx
Dá:
BEGIN blah END BEGIN 1
2
END
BEGIN foo END
BEGIN
bar
END BEGIN
baz END
Ambos precisam armazenar tudo, desde o BEGIN até o seguinte END na memória. Portanto, se você tiver um arquivo enorme cuja primeira linha contenha BEGIN, mas sem END, o arquivo inteiro será armazenado na memória para nada.
A única maneira de contornar isso seria processar o arquivo duas vezes, mas é claro que isso só poderia ser feito quando a entrada fosse um arquivo normal (não um canal, por exemplo).
Responder4
Abordagem GNU awk. O resultado é alcançado através da definição de variáveis específicas quando o cabeçalho inicial é encontrado. Algumas variáveis podem ser encurtadas por conveniência
$ 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 *****
Com o sinalizador END ausente, o resultado é nulo conforme esperado:
$ 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