¿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 fooalgún lugar dentro de él.
Un regex like (<tag1>.*?foo.*?</tag1>)debería dar la parte correcta, pero las herramientas me gustan grepy sedsolo 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
-Pindicador (perl-regex) y activándoloPCRE_DOTALLcon(?s)Si lo anterior no funciona en su plataforma, intente pasar la
-zbandera 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.14en Debian. Copié el ejemplo de OP como está (agregando solo la nueva línea final) y ejecuté sugreppero no obtuve resultados.grep -ozPlugar degrep -oPen 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
sedespacio de patrón que se vea así:Puede imprimir su espacio de patrón cuando lo desee con
look. Luego puede abordar los\ncaracteres.Le mostrará que cada línea la
sedprocesa en la etapa en la quelse llama.Así que lo acabo de probar y necesitaba uno más
\backslashdespués,commade la primera línea, pero por lo demás funciona como está. Aquí lo puse en un_sed_functionpara 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
ppor unlpara 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<<\SCRIPTsimplemente se vea así:Luego lo volveré a ejecutar:
¡Okay! Así que tenía razón, es un buen sentimiento. Ahora, barajemos nuestro
look para ver las líneas que tira pero elimina. Eliminaremos nuestra actually 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
Hviejo espacio a medida que lo construimos. Hay un par de conceptos clave que espero poder demostrar. Así que elimino el últimolook nuevamente y modifico la primera línea para agregar un vistazo alHespacio anterior al final:Hel 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, soloxcambio 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
dporque 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,
Hpero 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
sedguiones.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$ase establece en1si se encuentra una etiqueta de apertura (<tag1>).if($a==1){push @l,$_}: para cada línea, si$aes 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$avolver a establecer en 0.fuente
<tag1>confooy funciona bien. ¿Cuándo falla para ti?Aquí hay una
sedalternativa:Explicación
-nsignifica no imprimir líneas a menos que se le indique./<tag1/primero coincide con la etiqueta de apertura:xes una etiqueta para permitir saltar a este punto más tardeNagrega la siguiente línea al espacio del patrón (búfer activo)./<\/tag1/!b xsignifica que si el espacio del patrón actual no contiene una etiqueta de cierre, bifurca a laxetiqueta creada anteriormente. Por lo tanto, seguimos agregando líneas al espacio del patrón hasta que encontramos nuestra etiqueta de cierre./foo/psignifica 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
grepadmite, también puede usar la-Copció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.