Cómo extraer cadenas siguiendo un patrón con grep, regex o perl

90

Tengo un archivo que se parece a esto:

    <table name="content_analyzer" primary-key="id">
      <type="global" />
    </table>
    <table name="content_analyzer2" primary-key="id">
      <type="global" />
    </table>
    <table name="content_analyzer_items" primary-key="id">
      <type="global" />
    </table>

Necesito extraer cualquier cosa entre las comillas que siguen name=, es decir content_analyzer, content_analyzer2y content_analyzer_items.

Estoy haciendo esto en una caja de Linux, por lo que una solución que use sed, perl, grep o bash está bien.

vaquero
fuente
5
no hay necesidad de ser tímido, ¡bienvenido aquí!
Benoit
8
Siento que estaría mal no vincular a stackoverflow.com/questions/1732348/…
Christoffer Hammarström
Gracias a todos por los útiles comentarios. Pido disculpas por el formato incorrecto del XML. Eliminé algunas etiquetas para simplificar.
wrangler

Respuestas:

167

Dado que necesita hacer coincidir el contenido sin incluirlo en el resultado (debe coincidir, name=" pero no es parte del resultado deseado) se requiere alguna forma de coincidencia de ancho cero o captura de grupo. Esto se puede hacer fácilmente con las siguientes herramientas:

Perl

Con Perl, puede usar la n opción para recorrer línea por línea e imprimir el contenido de un grupo de captura si coincide:

perl -ne 'print "$1\n" if /name="(.*?)"/' filename

GNU grep

Si tiene una versión mejorada de grep, como GNU grep, puede tener la -Popción disponible. Esta opción habilitará una expresión regular similar a Perl, lo que le permitirá usar \Kuna búsqueda retrospectiva abreviada. Restablecerá la posición de coincidencia, por lo que cualquier cosa antes de que sea de ancho cero.

grep -Po 'name="\K.*?(?=")' filename

La o opción hace que grep imprima solo el texto coincidente, en lugar de la línea completa.

Vim - Editor de texto

Otra forma es utilizar un editor de texto directamente. Con Vim, una de las diversas formas de lograr esto sería eliminar las líneas sin name=y luego extraer el contenido de las líneas resultantes:

:v/.*name="\v([^"]+).*/d|%s//\1

Grep estándar

Si no tiene acceso a estas herramientas, por alguna razón, se podría lograr algo similar con grep estándar. Sin embargo, sin mirar alrededor, será necesario limpiarlo más tarde:

grep -o 'name="[^"]*"' filename

Una nota sobre cómo guardar resultados

En todos los comandos anteriores, se enviarán los resultados a stdout. Es importante recordar que siempre puede guardarlos conectándolos a un archivo agregando:

> result

hasta el final del comando.

Sidyll
fuente
12
Lookarounds (en GNU grep):grep -Po '.*name="\K.*?(?=".*)'
Pausado hasta nuevo aviso.
@Dennis Williamson, genial. Actualicé la respuesta en consecuencia, pero dejé ambas a .*un lado, espero que no se enoje conmigo. Me gustaría preguntar, ¿ve algún beneficio de un partido sin codicia sobre "cualquier cosa excepto ""? No tome esto como una pelea, solo tengo curiosidad y no soy un experto en expresiones regulares. Además, la \Kpropina, muy bonita. Gracias Dennis.
Sidyll
2
¿Por qué estaría enojado? Sin el .*, puedes hacerlo grep -Po '(?<=name=").*?(?=")'. Se \Kpuede usar para taquigrafía, pero en realidad solo es necesario si la coincidencia a su izquierda es de longitud variable. En casos como este, la razón para usar alternativas es bastante obvia. Las operaciones poco codiciadas se ven un poco más ordenadas ( [^"]*versus .*?y no tienes que repetir el personaje principal. No sé sobre la velocidad. Eso depende mucho del contexto, creo. Espero que sea útil.
Pausado hasta nuevo aviso.
@Dennis Williamson: ciertamente señor, aquí hay mucha información útil. Creo que la razón por la que guardé el \K(después de investigarlo) y eliminé el .*fue el mismo: hacer que se vea bonito (más simple). Y nunca pensé en usar en .*?lugar de la "forma tradicional" que aprendí de alguna parte. Pero la falta de codicia aquí realmente tiene sentido. Gracias Dennis, mis mejores deseos.
sidyll
+1 para describir el comando. Le agradecería si pudiera actualizar su respuesta para explicar la parte "[...]" de la expresión regular.
Lreeder
5

La expresión regular sería:

.+name="([^"]+)"

Entonces la agrupación estaría en el \ 1

Matt Shaver
fuente
5

Si está utilizando Perl, descargue un módulo para analizar XML: XML :: Simple , XML :: Twig o XML :: LibXML . No reinventes la rueda.

shawnhcorey
fuente
3
Tenga en cuenta que el ejemplo que dio OP no está bien formado ( <type="global"por ejemplo), por lo que la mayoría de los analizadores XML simplemente se quejan y mueren.
bvr
5

Se debe utilizar un analizador HTML para este propósito en lugar de expresiones regulares. Un programa de Perl que utiliza HTML::TreeBuilder:

Programa

#!/usr/bin/env perl

use strict;
use warnings;

use HTML::TreeBuilder;

my $tree = HTML::TreeBuilder->new_from_file( \*DATA );
my @elements = $tree->look_down(
    sub { defined $_[0]->attr('name') }
);

for (@elements) {
    print $_->attr('name'), "\n";
}

__DATA__
<table name="content_analyzer" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer2" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer_items" primary-key="id">
  <type="global" />
</table>

Salida

content_analyzer
content_analyzer2
content_analyzer_items
Alan Haggai Alavi
fuente
2

esto podría hacerlo:

perl -ne 'if(m/name="(.*?)"/){ print $1 . "\n"; }'
Benoit
fuente
2

Aquí hay una solución que utiliza HTML tidy y xmlstarlet:

htmlstr='
<table name="content_analyzer" primary-key="id">
<type="global" />
</table>
<table name="content_analyzer2" primary-key="id">
<type="global" />
</table>
<table name="content_analyzer_items" primary-key="id">
<type="global" />
</table>
'

echo "$htmlstr" | tidy -q -c -wrap 0 -numeric -asxml -utf8 --merge-divs yes --merge-spans yes 2>/dev/null |
sed '/type="global"/d' |
xmlstarlet sel -N x="http://www.w3.org/1999/xhtml" -T -t -m "//x:table" -v '@name' -n
mitma
fuente
1

Vaya, el comando sed debe preceder al comando tidy, por supuesto:

echo "$htmlstr" | 
sed '/type="global"/d' |
tidy -q -c -wrap 0 -numeric -asxml -utf8 --merge-divs yes --merge-spans yes 2>/dev/null |
xmlstarlet sel -N x="http://www.w3.org/1999/xhtml" -T -t -m "//x:table" -v '@name' -n
mitma
fuente
0

Si la estructura de su xml (o texto en general) es fija, la forma más fácil es usar cut. Para su caso específico:

echo '<table name="content_analyzer" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer2" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer_items" primary-key="id">
  <type="global" />
</table>' | grep name= | cut -f2 -d '"'
Carlos Lindado
fuente