Imprime líneas entre el patrón inicial y final, pero si el patrón final no existe, no imprimas

Imprime líneas entre el patrón inicial y final, pero si el patrón final no existe, no imprimas

Estoy buscando encontrar las líneas entre dos patrones coincidentes. Si falta algún patrón inicial o final, las líneas no deberían imprimirse.

Entrada correcta:

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

La salida será

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

Ahora supongamos que falta el patrón END en la entrada

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

Las líneas no deben imprimirse.

Lo he probado con sed:

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

Imprime todos los datos hasta la última línea si falta el patrón FINAL.

¿Cómo resolverlo?

Respuesta1

Puede lograr esto de la siguiente manera:

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

Su funcionamiento es que, para el rango inicial/final de líneas, las almacena en un espacio de espera. Luego elimina hasta que llegue a la línea FINAL. En ese momento recordamos lo que está en suspenso. OTW, no sacamos nada. HTH.

Respuesta2

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

Funciona invirtiendo taclas líneas para sedpoder encontrar ambos delimitadores en ambos órdenes.

Respuesta3

Con pcregrep:

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

Eso también funciona si BEGIN y END están en la misma línea, pero no en casos como:

BEGIN 1 END foo BEGIN 2
END

Donde pcregrepatrapa el primero BEGIN 1 END, pero no el segundo.

Para manejarlos, con awk, podrías hacer:

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
  }'

En una entrada como:

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

Da:

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

Ambos necesitan almacenar todo, desde el COMIENZO hasta el siguiente FINAL en la memoria. Entonces, si tiene un archivo enorme cuya primera línea contiene BEGIN pero sin FIN, todo el archivo se almacenará en la memoria sin costo alguno.

La única forma de evitarlo sería procesar el archivo dos veces, pero, por supuesto, eso solo se puede hacer cuando la entrada es un archivo normal (no una tubería, por ejemplo).

Respuesta4

Enfoque GNU awk. El resultado se logra estableciendo variables particulares cuando se encuentra el encabezado de inicio. Algunas variables se pueden acortar por conveniencia.

$ 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 *****

Si falta el indicador END, el resultado es nulo como se esperaba:

$ 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

información relacionada