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
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 confoo
y haz lo que viene dentro de los garabatos en las líneas coincidentes. Reemplazafoo
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 conbar
. 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 etiquetastart
que creamos antes para seguir agregando la siguiente línea siempre que el espacio del patrón no contengabar
. -
/your_regex/p
imprime el espacio del patrón si coincide conyour_regex
. Debe reemplazaryour_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íadef
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 quesecond_line_word
está en la tercera línea, entonces no solo falta la primera línea (debido a su segundogrep
) 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 variableall
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
-
/foo/{
se activa cuando la línea contiene «foo» -
n
reemplace el espacio del patrón con la siguiente línea, es decir, la palabra «aquí» -
b gotoloop
bifurque a la etiqueta «gotoloop» -
:gotoloop
define la etiqueta «gotoloop» -
/bar/!{
si el patrón no «contiene» barra « -
h
reemplace el espacio de espera con el patrón, por lo que «aquí» se guarda en el espacio de espera -
b loop
bifurca a la etiqueta «loop» -
:loop
define la etiqueta «loop» -
N
agrega el patrón al espacio de espera.
Ahora el espacio de espera contiene:
«aquí»
«es el» -
:gotoloop
Ahora estamos en el paso 4, y repite hasta que una línea contiene «bar» -
/bar/
el ciclo finaliza, se ha encontrado «bar», » s el espacio del patrón - 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
-
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.
grep
. Están estrechamente relacionados pero no dups, IMO."grep"
sugiriendo el verbo » para grep «, y las respuestas principales, incluyendo aceptado, no ‘ t use grep.