¿Cómo puedo "grep" patrones en varias líneas?


24

Parece que estoy haciendo mal uso 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. Originalmente pensé que mis expresiones regulares estaban mal, pero finalmente leí que estas herramientas funcionan por línea (también mis expresiones regulares eran tan triviales que no podía ser el problema).

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



1
@CiroSantilli: no creo que esta Q y la que vinculaste sean duplicados. La otra Q pregunta cómo haría la coincidencia de patrones de varias líneas (es decir, qué herramienta debería / puedo usar para hacer esto), mientras que esta pregunta cómo hacer esto grep. Están estrechamente relacionados pero no dups, en mi opinión.
slm

@sim esos casos son difíciles de decidir: puedo ver tu punto. Creo que este caso particular es mejor como un duplicado porque el usuario dijo que "grep"sugiere el verbo "to grep", y las respuestas principales, incluidas las aceptadas, no usan grep.
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Respuestas:


24

Aquí hay seduno que le dará un grepcomportamiento similar 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 fooy haga lo que viene dentro de los garabatos a las líneas coincidentes. Reemplace foocon la parte inicial del patrón.
  • :start es una etiqueta de ramificación que nos ayuda a seguir en bucle hasta que encontremos el final de nuestra expresión regular.
  • /bar/!{}ejecutará lo que hay en los squigglies a las líneas que no coinciden bar. Reemplazar barcon la parte final del patrón.
  • Nagrega la siguiente línea al búfer activo ( sedllama a esto el espacio del patrón)
  • b startse ramificará incondicionalmente a la startetiqueta que creamos anteriormente para seguir agregando la siguiente línea siempre que el espacio del patrón no contenga bar.
  • /your_regex/pimprime el espacio del patrón si coincide your_regex. Debería reemplazarlo your_regexpor la expresión completa que desea hacer coincidir en varias líneas.

1
+1 ¡Agregando esto al toolikt! Gracias.
wmorrison365

Nota: en MacOS esto dased: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
Stan James

1
Obteniendo sed: unterminated {error
Nomaed

@Nomaed Shot en la oscuridad aquí, pero ¿su expresión regular contiene algún carácter "{"? Si es así, deberás retroceder y escapar de ellos.
Joseph R.

1
@Nomaed Parece que tiene que ver con las diferencias entre sedimplementaciones. Traté de seguir las recomendaciones de esa respuesta para que el script anterior cumpliera con los estándares, pero me dijo que "inicio" era una etiqueta indefinida. Por lo tanto, no estoy seguro de si esto se puede hacer de una manera que cumpla con los estándares. Si lo logras, no dudes en editar mi respuesta.
Joseph R.

19

Generalmente uso una herramienta llamada pcregrepque se puede instalar en la mayoría de los sabores de Linux usando yumo apt.

Por ej.

Supongamos que si tiene un archivo testfilecon 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 puedes hacer lo mismo sed.

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

5

Aquí hay un enfoque más simple con Perl:

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

o (como JosephR tomó la sedruta , robaré su sugerencia descaradamente )

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

Explicación

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

La -0fija el separador de registro de entrada. Establecer esto para 00activar el 'modo de párrafo' donde Perl usará nuevas líneas consecutivas ( \n\n) como separador de registros. En los casos en que no hay nuevas líneas consecutivas, todo el archivo se lee (sorbe) a la vez.

Advertencia:

No no hacer esto para archivos de gran tamaño, se carga el archivo en la memoria y que puede ser un problema.


2

Una forma de hacerlo es con Perl. Por ejemplo, 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 algunos Perl que coincidirán con cualquier línea que comience con foo seguida de cualquier línea que comience con barra:

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 alltiene la expresión regular ...
  • /^(foo[^\n]*\nbar[^\n]*\n)/mEl regex: foo al comienzo de la línea, seguido de cualquier número de caracteres que no sean de nueva línea, seguido de una nueva línea, seguida inmediatamente por "barra", y el resto de la línea con barra en ella. /mal final de la expresión regular significa "hacer coincidir varias líneas"
  • print $1 Imprima 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 para la expresión regular, de modo que podamos hacer coincidir múltiples casos de la expresión regular en el archivo en cuestión

Y la salida:

foo line 1
bar line 2
foo
bar line 6

3
Acabo de pasar para decir que su Perl puede acortarse a la más idiomática:perl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo
Joseph R.

2

La alternativa grep tamizar admite la coincidencia de varias líneas (exención de responsabilidad: yo soy el autor).

Supongamos que testfilecontiene:

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


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

Resultado:

archivo de prueba: <descripción> Lorem ipsum dolor sit amet, consectetur
archivo de prueba: adipiscing elit, sed do eiusmod tempor incididunt ut
archivo de prueba: labore et dolore magna aliqua </description>


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

Resultado:

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

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

2

Simplemente un grep normal que admite Perl-regexpparámetros Phará 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.


Cuando pruebo esta solución, la salida no termina en 'def' sino que va al final del archivo 'blah'
buckley

tal vez su grep no es compatible con la -Popción
Avinash Raj

1

Resolví este para mí usando grep y la opción -A 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 combinación de archivo y palabra. Pero para mí fue la solución más rápida y confiable.


alias grepp = 'grep --color = auto -B10 -A20 -i' y luego cat somefile | grepp blah | grepp foo | grepp bar ... sí, esos -A y -B son muy útiles ... tienes la mejor respuesta
Scott Stensland

1

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 usar 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

1

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 usar 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? Vamos a hacerlo 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 pasar 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 bifurcarse a la etiqueta "loop"
  8. :loop define la etiqueta "loop"
  9. N agrega el patrón al espacio de espera.
    Ahora mantenga el espacio contiene:
    "aquí"
    "es el"
  10. :gotoloop Ahora estamos en el paso 4 y recorremos hasta que una línea contenga "barra"
  11. /bar/ el ciclo está terminado, se ha encontrado la "barra", es el espacio del patrón
  12. g el espacio del patrón se reemplaza con el espacio de espera que contiene todas las líneas entre "foo" y "bar" que se han guardado durante el bucle principal
  13. p copia el espacio del patrón a la salida estándar

Hecho !


Bien hecho, +1. Por lo general, evito usar estos comandos introduciendo las nuevas líneas en SOH y ejecutando comandos sed normales y luego reemplazo las nuevas líneas.
A.Danischewski
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.