¿Cómo obtener múltiples líneas de un archivo por una expresión regular?
A menudo me gustaría obtener varias líneas / modificar varias líneas por una expresión regular. Un caso de ejemplo:
Estoy tratando de leer parte de un archivo XML / SGML (no están necesariamente bien formados o tienen una sintaxis predecible, por lo que una expresión regular sería más segura que un analizador adecuado. Además, me gustaría poder hacer esto también completamente archivos no estructurados donde solo se conocen algunas palabras clave.) en un script de shell (que se ejecuta en Solaris y Linux).
XML de ejemplo:
<tag1>
<tag2>bar</tag2>
</tag1>
<tag1>
<tag2>foo</tag2>
</tag1>
De esto me gustaría leer <tag1>
si contiene foo
algún lugar dentro de él.
Un regex like (<tag1>.*?foo.*?</tag1>)
debería dar la parte correcta, pero las herramientas me gustan grep
y sed
solo funcionan para mí con líneas simples. Como puedo conseguir
<tag1>
<tag2>foo</tag2>
</tag1>
¿en este ejemplo?
Respuestas:
Si tiene instalado GNU grep, puede hacer una búsqueda multilínea pasando el
-P
indicador (perl-regex) y activándoloPCRE_DOTALL
con(?s)
Si lo anterior no funciona en su plataforma, intente pasar la
-z
bandera además, esto obliga a grep a tratar NUL como separador de línea, haciendo que todo el archivo se vea como una sola línea.fuente
(?s)
consejo(GNU grep) 2.14
en Debian. Copié el ejemplo de OP como está (agregando solo la nueva línea final) y ejecuté sugrep
pero no obtuve resultados.grep -ozP
lugar degrep -oP
en tus plataformas?Si hace lo anterior, dados los datos que muestra, antes de la última línea de limpieza allí, debería estar trabajando con un
sed
espacio de patrón que se vea así:Puede imprimir su espacio de patrón cuando lo desee con
l
ook. Luego puede abordar los\n
caracteres.Le mostrará que cada línea la
sed
procesa en la etapa en la quel
se llama.Así que lo acabo de probar y necesitaba uno más
\backslash
después,comma
de la primera línea, pero por lo demás funciona como está. Aquí lo puse en un_sed_function
para que pueda llamarlo fácilmente con fines de demostración a lo largo de esta respuesta: (funciona con comentarios incluidos, pero aquí se eliminan por razones de brevedad)Ahora cambiaremos el
p
por unl
para que podamos ver con qué estamos trabajando mientras desarrollamos nuestro script y eliminamos la demostración no operativas?
para que la última línea de nuestrosed 3<<\SCRIPT
simplemente se vea así:Luego lo volveré a ejecutar:
¡Okay! Así que tenía razón, es un buen sentimiento. Ahora, barajemos nuestro
l
ook para ver las líneas que tira pero elimina. Eliminaremos nuestra actuall
y agregaremos una para!{block}
que se vea así:Eso es lo que parece justo antes de que lo eliminemos.
Una última cosa que quiero mostrarles es el
H
viejo espacio a medida que lo construimos. Hay un par de conceptos clave que espero poder demostrar. Así que elimino el últimol
ook nuevamente y modifico la primera línea para agregar un vistazo alH
espacio anterior al final:H
el espacio antiguo sobrevive a los ciclos de línea, de ahí el nombre. Entonces, lo que la gente a menudo tropieza, está bien, lo que a menudo tropiezo es que necesita eliminarse después de usarlo. En este caso, solox
cambio e una vez, por lo que el espacio de espera se convierte en el espacio del patrón y viceversa, y este cambio también sobrevive a los ciclos de línea.El efecto es que necesito eliminar mi espacio de espera que solía ser mi espacio de patrón. Hago esto limpiando primero el espacio de patrón actual con:
Que simplemente selecciona cada personaje y lo elimina. No puedo usarlo
d
porque esto terminaría mi ciclo de línea actual y el siguiente comando no se completaría, lo que prácticamente destruiría mi script.Esto funciona de manera similar,
H
pero sobrescribe el espacio de retención, por lo que acabo de copiar mi espacio de patrón en blanco sobre la parte superior de mi espacio de retención, eliminándolo efectivamente. Ahora solo puedo:fuera.
Y así es como escribo
sed
guiones.fuente
La respuesta de @jamespfinn funcionará perfectamente bien si su archivo es tan simple como su ejemplo. Si tiene una situación más compleja donde
<tag1>
podría abarcar más de 2 líneas, necesitará un truco un poco más complejo. Por ejemplo:El script perl procesará cada línea de su archivo de entrada y
if(/<tag1>/){$a=1;}
: la variable$a
se establece en1
si se encuentra una etiqueta de apertura (<tag1>
).if($a==1){push @l,$_}
: para cada línea, si$a
es así1
, agregue esa línea a la matriz@l
.if(/<\/tag1>/)
: si la línea actual coincide con la etiqueta de cierre:if(grep {/foo/} @l){print "@l"}
: si alguna de las líneas guardadas en la matriz@l
(estas son las líneas entre<tag1>
y</tag1>
) coincide con la cadenafoo
, imprima el contenido de@l
.$a=0; @l=()
: vaciar la lista (@l=()
) y$a
volver a establecer en 0.fuente
<tag1>
confoo
y funciona bien. ¿Cuándo falla para ti?Aquí hay una
sed
alternativa:Explicación
-n
significa no imprimir líneas a menos que se le indique./<tag1/
primero coincide con la etiqueta de apertura:x
es una etiqueta para permitir saltar a este punto más tardeN
agrega la siguiente línea al espacio del patrón (búfer activo)./<\/tag1/!b x
significa que si el espacio del patrón actual no contiene una etiqueta de cierre, bifurca a lax
etiqueta creada anteriormente. Por lo tanto, seguimos agregando líneas al espacio del patrón hasta que encontramos nuestra etiqueta de cierre./foo/p
significa que si el espacio del patrón actual coincidefoo
, debe imprimirse.fuente
Creo que podría hacerlo con GNU awk, tratando la etiqueta final como un separador de registros, por ejemplo, para una etiqueta final conocida
</tag1>
:o más generalmente (con una expresión regular para la etiqueta final)
Probándolo en @ terdon
foo.xml
:fuente
Si su archivo está estructurado exactamente como se muestra arriba, puede utilizar los indicadores -A (líneas después) y -B (líneas antes) para grep ... por ejemplo:
Si su versión de lo
grep
admite, también puede usar la-C
opción más simple (para el contexto) que imprime las N líneas circundantes:fuente
tail -3 input_file.xml
. Sí, funciona para este ejemplo específico, pero no es una respuesta útil a la pregunta.