Parece que estoy haciendo un mal uso de grep / egrep.

Estaba tratando de buscar cadenas en varias líneas y no pude encontrar una coincidencia mientras sé que lo que estoy buscando debería coincidir. Al principio pensé que mis expresiones regulares eran incorrectas, pero finalmente leí que estas las herramientas operan por línea (también mis expresiones regulares eran tan triviales que no podrían ser el problema).

Entonces, ¿qué herramienta se usaría para buscar patrones en varias líneas?

Comentarios

  • posible duplicado de Coincidencia de patrón multilínea usando sed, awk o grep
  • @CiroSantilli – No creo que esta Q y la que vinculó son duplicados. La otra Q es preguntar cómo ‘ harías una coincidencia de patrones de varias líneas (es decir, qué herramienta debería / puedo usar para hacer esto) mientras que este pregunta cómo hacer esto con grep. Están estrechamente relacionados pero no dups, IMO.
  • @sim esos casos son difícil de decidir: entiendo tu punto. Creo que este caso en particular es mejor como Vea que el usuario dijo "grep" sugiriendo el verbo » para grep «, y las respuestas principales, incluyendo aceptado, no ‘ t use grep.
  • No hay ninguna indicación que muestre que se necesita una expresión regular de varias líneas aquí. Considere mostrar un ejemplo real con datos de entrada y datos de salida esperados, así como su esfuerzo anterior.

Respuesta

Aquí «sa sed uno que le dará un comportamiento grep en varias líneas:

sed -n "/foo/{:start /bar/!{N;b start};/your_regex/p}" your_file 

Cómo funciona

  • -n suprime el comportamiento predeterminado de imprimir cada línea
  • /foo/{} le indica que coincida con foo y haz lo que viene dentro de los garabatos en las líneas coincidentes. Reemplaza foo con la parte inicial del patrón.
  • :start es una etiqueta de ramificación que nos ayuda a seguir repitiendo hasta encontrar el final de nuestra expresión regular.
  • /bar/!{} ejecutará lo que «s en los garabatos para las líneas que no coinciden con bar. Reemplaza con la parte final del patrón.
  • N agrega la siguiente línea al búfer activo (sed llama a esto el espacio de patrón)
  • b start se ramificará incondicionalmente a la etiqueta start que creamos antes para seguir agregando la siguiente línea siempre que el espacio del patrón no contenga bar.
  • /your_regex/p imprime el espacio del patrón si coincide con your_regex. Debe reemplazar your_regex por la expresión completa que desea hacer coincidir en varias líneas.

Comentarios

  • +1 ¡Añadiendo esto al toolikt! Gracias.
  • Nota: En MacOS, esto da sed: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
  • Obteniendo sed: unterminated { error
  • @Nomaed Disparo en la oscuridad aquí, pero ¿su expresión regular contiene algún » {» caracteres? Si es así, ‘ necesitará escapar de ellos.
  • @Nomaed Parece que tiene que ver con las diferencias entre sed implementaciones. Intenté seguir las recomendaciones en esa respuesta para hacer que el script anterior cumpla con el estándar, pero me dijo que » start » no estaba definido. etiqueta. Entonces ‘ no estoy seguro de si esto se puede hacer de una manera compatible con los estándares. Si lo gestiona, no dude en editar mi respuesta.

Respuesta

Por lo general, uso una herramienta llamado pcregrep que se puede instalar en la mayoría de versiones de Linux usando yum o apt.

Por ejemplo.

Suponga que tiene un archivo llamado testfile con contenido

abc blah blah blah def blah blah blah 

Puede ejecutar el siguiente comando:

$ pcregrep -M "abc.*(\n|.)*def" testfile 

para hacer coincidir patrones en varias líneas.

Además, también puede hacer lo mismo con sed.

$ sed -e "/abc/,/def/!d" testfile 

Comentarios

  • esta sed omite la sugerencia la línea donde se encontraría def

Responder

Simplemente un grep normal que admita Perl-regexp parámetro P hará este trabajo.

$ echo "abc blah blah blah def blah blah blah" | grep -oPz "(?s)abc.*?def" abc blah blah blah def 

(?s) llamado modificador DOTALL que hace que el punto en su expresión regular coincida no solo con los caracteres sino también con los saltos de línea.

Comentarios

  • Cuando pruebo esta solución, la salida no termina en ‘ def ‘ pero va al final del archivo ‘ blah ‘
  • tal vez su grep no admite la opción -P
  • Esta fue la única que funcionó para mí – probé todas las sed sugerencias, pero no ‘ llegué a instalar alternativas grep.
  • $ grep --version: grep (GNU grep) 3.1 en Windows Git Bash tiene una opción -P, --perl-regexp pero (?s) no ‘ no parece funcionar allí. Todavía muestra solo la primera línea. El mismo patrón con la misma cadena de prueba funciona en regex101.com . ¿Existe una alternativa en Git Bash? sed? (sed (GNU sed) 4.8 aquí)
  • ¿Sabe cómo agregar contexto a la salida? grep -1 no ‘ no funciona aquí.

Responder

Aquí «un enfoque más simple usando Perl:

perl -e "$f=join("",<>); print $& if $f=~/foo\nbar.*\n/m" file 

o (dado que JosephR tomó el sed route , robaré descaradamente su sugerencia )

perl -n000e "print $& while /^foo.*\nbar.*\n/mg" file 

### Explicación

$f=join("",<>);: esto lee el archivo completo y guarda su contenido (líneas nuevas y todo) en la variable $f. Luego intentamos hacer coincidir foo\nbar.*\n e imprimirlo si coincide (la variable especial $& contiene la última coincidencia encontrada). El ///m es necesario para hacer que la expresión regular coincida entre líneas nuevas.

El -0 establece el separador de registros de entrada. Establecer esto en 00 activa el «modo de párrafo» donde Perl usará nuevas líneas consecutivas (\n\n) como separador de registros. En los casos en los que no haya nuevas líneas consecutivas, todo el archivo se lee (sorbe) a la vez.

### Advertencia: no haga esto para archivos grandes, se cargará el archivo completo en la memoria y eso puede ser un problema.

Comentarios

  • No ‘ t ¿Sabes mucho sobre Perl, pero no ‘ no necesita ser my $f=join("",<>);, estrictamente hablando?
  • @Sapphire_Brick solamente si está en modo estricto (use strict;). Es ‘ un buen hábito para adquirir, especialmente cuando se escriben scripts más grandes, pero ‘ es un éxito para una pequeña frase como esta. uno.

Respuesta

Supongamos que tenemos el archivo test.txt que contiene:

blabla blabla foo here is the text to keep between the 2 patterns bar blabla blabla 

Se puede utilizar el siguiente código:

sed -n "/foo/,/bar/p" test.txt 

Para el siguiente resultado:

foo here is the text to keep between the 2 patterns bar 

Respuesta

La alternativa grep sift admite la coincidencia multilínea (descargo de responsabilidad: soy el autor).

Supongamos testfile contiene:

 <book> <title>Lorem Ipsum</title> <description>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua</description> </book> 

sift -m '<description>.*?</description>' (muestra las líneas que contienen la descripción)

Resultado:

 testfile: <description>Lorem ipsum dolor sit amet, consectetur testfile: adipiscing elit, sed do eiusmod tempor incididunt ut testfile: labore et dolore magna aliqua</description> 

sift -m '<description>(.*?)</description>' --replace 'description="$1"' --no-filename (extraer y reformatear la descripción)

Resultado:

description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" 

Comentarios

  • Muy buena herramienta. ¡Felicidades! Intenta incluirlo en distribuciones como Ubuntu.

Respuesta

Resolví este por mí usando grep y – Una opción con otro grep.

grep first_line_word -A 1 testfile | grep second_line_word 

La opción -A 1 imprime 1 línea después de la línea encontrada. Por supuesto, depende de su archivo y combinación de palabras. Pero para mí fue la solución más rápida y confiable.

Comentarios

  • alias grepp = ‘ grep –color = auto -B10 -A20 -i ‘ luego cat somefile | grepp bla | grepp foo | grepp bar … sí, esos -A y -B son muy útiles …tienes la mejor respuesta
  • Esto no es ‘ t superdeterminista e ignora el patrón completo a favor de obtener una única línea diferente (solo en función de su proximidad a la primera línea). Es ‘ mejor decirle al programa que vaya tan lejos como sea necesario para llegar a algún tipo de patrón que ‘ re absolutamente seguro es el final del texto que ‘ estás intentando hacer coincidir. Por ejemplo, si testfile se actualiza de modo que second_line_word está en la tercera línea, entonces no solo falta la primera línea (debido a su segundo grep) pero ‘ no le falta la línea que comenzó a aparecer entre los dos.
  • Este sería un MO lo suficientemente bueno para comandos ad hoc donde realmente solo desea una sola línea en la salida que ya entendió. No ‘ no creo que ‘ sea lo que busca el OP y probablemente también podrías copiar / pegar en ese punto debido a siendo ad hoc.

Respuesta

Una forma de hacer esto es con Perl. p.ej. aquí está el contenido de un archivo llamado foo:

foo line 1 bar line 2 foo foo foo line 5 foo bar line 6 

Ahora, aquí hay algo de Perl que coincidir con cualquier línea que comience con foo seguida de cualquier línea que comience con bar:

cat foo | perl -e "while(<>){$all .= $_} while($all =~ /^(foo[^\n]*\nbar[^\n]*\n)/m) { print $1; $all =~ s/^(foo[^\n]*\nbar[^\n]*\n)//m; }" 

El Perl, desglosado:

  • while(<>){$all .= $_} Esto carga toda la entrada estándar en la variable $all
  • while($all =~ Mientras que la variable all tiene la expresión regular …
  • /^(foo[^\n]*\nbar[^\n]*\n)/m La expresión regular: foo al principio de la línea, seguido de cualquier número de caracteres que no sean de nueva línea, seguido de una nueva línea, seguido inmediatamente de «barra» y el resto de la línea con barra en ella. /m al final de la expresión regular significa «coincidencia en varias líneas»
  • print $1 Imprime la parte de la expresión regular que estaba entre paréntesis (en este caso, la expresión regular completa)
  • s/^(foo[^\n]*\nbar[^\n]*\n)//m Borre la primera coincidencia de la expresión regular, para que podamos hacer coincidir varios casos de la expresión regular en el archivo en cuestión

Y el resultado:

foo line 1 bar line 2 foo bar line 6 

Comentarios

  • Simplemente pasa por aquí para decir que tu Perl se puede acortar al más idiomático: perl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo

Respuesta

Si queremos obtener el texto entre los 2 patrones excluyéndose a sí mismos.

Supongamos que tenemos el archivo test.txt que contiene:

blabla blabla foo here is the text to keep between the 2 patterns bar blabla blabla 

Se puede utilizar el siguiente código:

 sed -n "/foo/{ n b gotoloop :loop N :gotoloop /bar/!{ h b loop } /bar/{ g p } }" test.txt 

Para el siguiente resultado:

here is the text to keep between the 2 patterns 

¿Cómo funciona? hágalo paso a paso

  1. /foo/{ se activa cuando la línea contiene «foo»
  2. n reemplace el espacio del patrón con la siguiente línea, es decir, la palabra «aquí»
  3. b gotoloop bifurque a la etiqueta «gotoloop»
  4. :gotoloop define la etiqueta «gotoloop»
  5. /bar/!{ si el patrón no «contiene» barra «
  6. h reemplace el espacio de espera con el patrón, por lo que «aquí» se guarda en el espacio de espera
  7. b loop bifurca a la etiqueta «loop»
  8. :loop define la etiqueta «loop»
  9. N agrega el patrón al espacio de espera.
    Ahora el espacio de espera contiene:
    «aquí»
    «es el»
  10. :gotoloop Ahora estamos en el paso 4, y repite hasta que una línea contiene «bar»
  11. /bar/ el ciclo finaliza, se ha encontrado «bar», » s el espacio del patrón
  12. el espacio del patrón se reemplaza con el espacio de retención que contiene todas las líneas entre» foo «y» bar «que se han guardado durante el ciclo principal
  13. p copiar el espacio del patrón a la salida estándar

¡Listo!

Comentarios

  • Bien hecho, +1. Por lo general, evito usar estos comandos tr ‘ ingresando las nuevas líneas en SOH y ejecutando comandos sed normales y luego reemplazando las nuevas líneas.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *