¿Cómo obtener todas las líneas entre la primera y la última aparición de patrones?

8

¿Cómo puedo recortar un archivo (flujo de entrada de pozo) para que solo obtenga las líneas que van desde la primera aparición del patrón foohasta la última aparición del patrón bar?

Por ejemplo, considere la siguiente entrada:

A line
like
foo
this 
foo
bar
something
something else
foo
bar
and
the
rest

Espero esta salida:

foo
this 
foo
bar
something
something else
foo
bar
rahmu
fuente
3
¿Transmisión de un solo paso o un archivo? Esto es mucho más fácil de hacer cuando se permite el acceso aleatorio. Con un archivo, solo encontrará el primero fooy el último bare imprimirá todo en el medio, si acaso. Con un flujo, tendría que leer hasta el primero foo, y almacenar en el búfer todas las líneas subsiguientes en la memoria hasta EOF, vaciando el búfer cada vez que barse ve a. Esto podría significar almacenar en búfer todo el flujo en la memoria.
jw013

Respuestas:

6
sed -n '/foo/{:a;N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba};'

La coincidencia del patrón sed /first/,/second/lee las líneas una por una. Cuando una línea coincide con /first/ella, la recuerda y espera la primera coincidencia para el /second/patrón. Al mismo tiempo, aplica todas las actividades especificadas para ese patrón. Después de que el proceso comienza una y otra vez hasta el final del archivo.

Eso no es lo que necesitamos. Necesitamos buscar la última coincidencia de /second/patrón. Por lo tanto, construimos una construcción que busca solo la primera entrada /foo/. Cuando se encuentra el ciclo acomienza. Agregamos una nueva línea al búfer de coincidencia con Ny verificamos si coincide con el patrón /bar/. Si lo hace, simplemente lo imprimimos y borramos el búfer de coincidencia y el salto de janyway al comienzo del ciclo con ba.

También debemos eliminar el símbolo de nueva línea después de limpiar el búfer con /^\n/s/^\n//. Estoy seguro de que hay una solución mucho mejor, desafortunadamente no se me ocurrió.

Espero que todo esté claro.

prisa
fuente
1
¡Funciona! Sería genial si pudieras guiarnos a través de la construcción de tal comando. Me sentiría tonto simplemente copiarlo / pegarlo desde algún sitio web en línea;)
rahmu
1
Lo siento, no publiqué la explicación con la respuesta. Ahora está en el post.
prisa el
En algunas sedversiones, por ejemplo, BSD sed (que es lo que se encuentra en Mac), las etiquetas deben ir seguidas de una nueva línea o un final de cadena, por lo que es necesario el siguiente ajuste: sed -n -e '/foo/{:a' -e 'N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba' -e '};' Esto también funciona en GNU sed, por lo que creo que esta modificación (múltiples -eargumentos poner fin a un argumento después de cada nombre de rama) es un buen hábito portátil para usar cuando se usan ramas en sed.
Comodín
4

Lo haría con un poco de Perl one-liner.

cat <<EOF | perl -ne 'BEGIN { $/ = undef; } print $1 if(/(foo.*bar)/s)'
A line
like
foo
this 
foo
bar
something
something else
foo
bar
and
the
rest
EOF

rendimientos

foo
this 
foo
bar
something
something else
foo
bar
usuario1146332
fuente
3
Si esto fuera código de golf, podría usarlo en Elugar de ey en -00777lugar del $/bit (consulte perlrun (1)). Lo que lo acortaría a:, perl -0777 -nE 'say /(foo.*bar)/s'todavía algo legible.
Thor
1
¡No sabía sobre estas banderas! ¡Estoy seguro de que especialmente -0[octal]encontrará su camino en mi flujo de trabajo! Gracias por eso
user1146332
3

Aquí hay una solución sed de GNU de dos pasos que no requiere mucha memoria:

< infile                                     \
| sed -n '/foo/ { =; :a; z; N; /bar/=; ba }' \
| sed -n '1p; $p'                            \
| tr '\n' ' '                                \
| sed 's/ /,/; s/ /p/'                       \
| sed -n -f - infile

Explicación

  • La primera sedinvocación pasa el archivo y encuentra la primera aparición fooy todas las apariciones posteriores de bar.
  • Estas direcciones se configuran en un nuevo sedscript con dos invocaciones de sedy una tr. La salida del tercero sedes [start_address],[end_address]p, sin los corchetes.
  • La invocación final de los sedpases infilenuevamente, imprimiendo las direcciones encontradas y todo lo demás.
Thor
fuente
2

Si el archivo de entrada cabe cómodamente en la memoria, manténgalo simple .

Si el archivo de entrada es enorme, puede usarlo csplitpara dividirlo en pedazos al principio fooy en cada uno de ellos barluego ensamblar los pedazos. Las piezas se llaman piece-000000000, piece-000000001etc. Elija un prefijo (aquí piece-) que no choque con otros archivos existentes.

csplit -f piece- -n 9 - '%foo%' '/bar/' '{*}' <input-file

(En sistemas que no sean Linux, tendrá que usar un gran número dentro de las llaves, por ejemplo {999999999}, y pasar la -kopción. Ese número es el número de barpiezas).

Puedes ensamblar todas las piezas cat piece-*, pero esto te dará todo después del primero foo. Quita esa última pieza primero. Dado que los nombres de archivo producidos por csplitno contienen ningún carácter especial, puede trabajarlos sin tomar ninguna precaución especial, por ejemplo, con

rm $(echo piece-* | sed 's/.* //')

o equivalente

rm $(ls piece-* | tail -n 1)

Ahora puede unir todas las piezas y eliminar los archivos temporales:

cat piece-* >output
rm piece-*

Si desea eliminar las piezas, ya que están concatenadas para ahorrar espacio en disco, hágalo en un bucle:

mv piece-000000000 output
for x in piece-?????????; do
  cat "$x" >>output; rm "$x"
done
Gilles 'SO- deja de ser malvado'
fuente
1

Aquí hay otra forma con sed:

sed '/foo/,$!d;H;/bar/!d;s/.*//;x;s/\n//' infile

Agrega cada línea en el /foo/,$rango ( !se deligen líneas que no están en este rango ) al Hespacio antiguo. Las líneas que no coinciden barse eliminan. En las líneas que coinciden, el espacio del patrón se vacía, se xcambia con el espacio de espera y se elimina la línea vacía principal en el espacio del patrón.

Con una entrada enorme y pocas ocurrencias de baresto, debería ser (mucho) más rápido que tirar de cada línea al espacio del patrón y luego, cada vez, verificar el espacio del patrón bar.
Explicado:

sed '/foo/,$!d                     # delete line if not in this range
H                                  # append to hold space
/bar/!d                            # if it doesn't match bar, delete 
s/.*//                             # otherwise empty pattern space and
x                                  # exchange hold buffer w. pattern space then
s/\n//                             # remove the leading newline
' infile

Claro, si este es un archivo (y cabe en la memoria) simplemente podría ejecutar:

 ed -s infile<<'IN'
.t.
/foo/,?bar?p
q
IN

porque ed puede buscar hacia adelante y hacia atrás.
Incluso podría leer una salida de comando en el búfer de texto si su shell admite la sustitución de procesos:

printf '%s\n' .t. /foo/,?bar?p q | ed -s <(your command)

o si no es así, con gnu ed:

printf '%s\n' .t. /foo/,?bar?p q | ed -s '!your command'
don_crissti
fuente
0

Usando cualquier awk en cualquier shell en cualquier sistema UNIX y sin leer todo el archivo o la secuencia de entrada en la memoria al mismo tiempo:

$ awk '
    f {
        rec = rec $0 ORS
        if (/bar/) {
            printf "%s", rec
            rec = ""
        }
        next
    }
    /foo/ { f=1; rec=$0 ORS }
' file
foo
this
foo
bar
something
something else
foo
bar
Ed Morton
fuente
0

Grep también podría hacerlo (bueno, GNU grep):

<infile grep -ozP '(?s)foo.*bar' | tr '\0' '\n'

<infile grep -ozP '        #  call grep to print only the matching section (`-o`)
                           #  use NUL for delimiter (`-z`) (read the whole file).
                           #  And using pcre regex.
(?s)foo.*bar               #  Allow the dot (`.`) to also match newlines.
' | tr '\0' '\n'           #  Restore the NULs to newlines.

Para la entrada del cuerpo de la pregunta:

$ <infile grep -ozP '(?s)foo.*bar' | tr '\0' '\n'
foo
this 
foo
bar
something
something else
foo
bar
Isaac
fuente