Imprima linhas entre o padrão inicial e final, mas se o padrão final não existir, não imprima

Imprima linhas entre o padrão inicial e final, mas se o padrão final não existir, não imprima

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 tacas linhas para sedencontrar 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 pcregreppega o primeiro BEGIN 1 END, mas não o segundo.

Para lidar com isso, com awkvocê 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

informação relacionada