¿Cómo analizar XML en Bash?

Resuelto asked hace 15 años • 0 respuestas

Idealmente lo que me gustaría poder hacer es:

cat xhtmlfile.xhtml |
getElementViaXPath --path='/html/head/title' |
sed -e 's%(^<title>|</title>$)%%g' > titleOfXHTMLPage.txt
 avatar May 21 '09 22:05
Aceptado

En realidad, esto es solo una explicación de la respuesta de Yuzem , pero no sentí que tanta edición debiera realizarse para otra persona, y los comentarios no permiten el formato, así que...

rdom () { local IFS=\> ; read -d \< E C ;}

Llamemos a eso "read_dom" en lugar de "rdom", espaciémoslo un poco y usemos variables más largas:

read_dom () {
    local IFS=\>
    read -d \< ENTITY CONTENT
}

Bien, entonces define una función llamada read_dom. La primera línea hace que IFS (el separador de campo de entrada) sea local para esta función y lo cambia a >. Eso significa que cuando lee datos en lugar de dividirlos automáticamente en espacios, tabulaciones o líneas nuevas, se dividen en '>'. La siguiente línea dice que se lea la entrada de la entrada estándar y, en lugar de detenerse en una nueva línea, deténgase cuando vea un carácter '<' (el -d para el indicador delimitador). Lo que se lee luego se divide usando IFS y se asigna a las variables ENTIDAD y CONTENIDO. Entonces toma lo siguiente:

<tag>value</tag>

La primera llamada para read_domobtener una cadena vacía (ya que '<' es el primer carácter). IFS lo divide en solo '', ya que no hay un carácter '>'. Luego, Read asigna una cadena vacía a ambas variables. La segunda llamada obtiene la cadena 'etiqueta>valor'. Luego, el IFS lo divide en los dos campos 'etiqueta' y 'valor'. Read luego asigna las variables como: ENTITY=tagy CONTENT=value. La tercera llamada obtiene la cadena '/tag>'. El IFS lo divide en los dos campos '/tag' y ''. Read luego asigna las variables como: ENTITY=/tagy CONTENT=. La cuarta llamada devolverá un estado distinto de cero porque hemos llegado al final del archivo.

Ahora su bucle while se limpió un poco para que coincida con lo anterior:

while read_dom; do
    if [[ $ENTITY = "title" ]]; then
        echo $CONTENT
        exit
    fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt

La primera línea simplemente dice: "mientras la función read_dom devuelve un estado cero, haga lo siguiente". La segunda línea comprueba si la entidad que acabamos de ver es "título". La siguiente línea refleja el contenido de la etiqueta. Las cuatro líneas salen. Si no era la entidad del título, el bucle se repite en la sexta línea. Redirigimos "xhtmlfile.xhtml" a la entrada estándar (para la read_domfunción) y redirigimos la salida estándar a "titleOfXHTMLPage.txt" (el eco de antes en el bucle).

Ahora se le da lo siguiente (similar a lo que se obtiene al incluir un depósito en S3) para input.xml:

<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Name>sth-items</Name>
  <IsTruncated>false</IsTruncated>
  <Contents>
    <Key>[email protected]</Key>
    <LastModified>2011-07-25T22:23:04.000Z</LastModified>
    <ETag>&quot;0032a28286680abee71aed5d059c6a09&quot;</ETag>
    <Size>1785</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
</ListBucketResult>

y el siguiente bucle:

while read_dom; do
    echo "$ENTITY => $CONTENT"
done < input.xml

Deberías obtener:

 => 
ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/" => 
Name => sth-items
/Name => 
IsTruncated => false
/IsTruncated => 
Contents => 
Key => [email protected]
/Key => 
LastModified => 2011-07-25T22:23:04.000Z
/LastModified => 
ETag => &quot;0032a28286680abee71aed5d059c6a09&quot;
/ETag => 
Size => 1785
/Size => 
StorageClass => STANDARD
/StorageClass => 
/Contents => 

Entonces, si escribiéramos un whilebucle como el de Yuzem:

while read_dom; do
    if [[ $ENTITY = "Key" ]] ; then
        echo $CONTENT
    fi
done < input.xml

Obtendríamos una lista de todos los archivos en el depósito S3.

EDITAR Si por alguna razón local IFS=\>no funciona para usted y lo configura globalmente, debe restablecerlo al final de la función como:

read_dom () {
    ORIGINAL_IFS=$IFS
    IFS=\>
    read -d \< ENTITY CONTENT
    IFS=$ORIGINAL_IFS
}

De lo contrario, cualquier división de línea que hagas más adelante en el script se estropeará.

EDITAR 2 Para dividir los pares de nombre/valor de atributo, puede aumentar de la read_dom()siguiente manera:

read_dom () {
    local IFS=\>
    read -d \< ENTITY CONTENT
    local ret=$?
    TAG_NAME=${ENTITY%% *}
    ATTRIBUTES=${ENTITY#* }
    return $ret
}

Luego escriba su función para analizar y obtener los datos que desea de esta manera:

parse_dom () {
    if [[ $TAG_NAME = "foo" ]] ; then
        eval local $ATTRIBUTES
        echo "foo size is: $size"
    elif [[ $TAG_NAME = "bar" ]] ; then
        eval local $ATTRIBUTES
        echo "bar type is: $type"
    fi
}

Luego mientras read_domllamas parse_dom:

while read_dom; do
    parse_dom
done

Luego se le da el siguiente ejemplo de marcado:

<example>
  <bar size="bar_size" type="metal">bars content</bar>
  <foo size="1789" type="unknown">foos content</foo>
</example>

Deberías obtener este resultado:

$ cat example.xml | ./bash_xml.sh 
bar type is: metal
foo size is: 1789

EDITAR 3, otro usuario dijo que estaba teniendo problemas con él en FreeBSD y sugirió guardar el estado de salida de lectura y devolverlo al final de read_dom como:

read_dom () {
    local IFS=\>
    read -d \< ENTITY CONTENT
    local RET=$?
    TAG_NAME=${ENTITY%% *}
    ATTRIBUTES=${ENTITY#* }
    return $RET
}

No veo ninguna razón por la que eso no debería funcionar.

chad avatar Aug 13 '2011 17:08 chad

Las herramientas de línea de comandos que se pueden llamar desde scripts de shell incluyen:

  • 4xpath : contenedor de línea de comandos alrededor del paquete 4Suite de Python

  • XMLStarlet

  • xpath: contenedor de línea de comandos alrededor de la biblioteca XPath de Perl

    sudo apt-get install libxml-xpath-perl
    
  • Xidel : funciona tanto con URL como con archivos. También funciona con JSON

También uso xmllint y xsltproc con pequeños scripts de transformación XSL para realizar procesamiento XML desde la línea de comandos o en scripts de shell.

Nat avatar May 21 '2009 18:05 Nat

Puedes hacerlo muy fácilmente usando solo bash. Sólo tienes que añadir esta función:

rdom () { local IFS=\> ; read -d \< E C ;}

Ahora puedes usar rdom como read pero para documentos html. Cuando se llama, rdom asignará el elemento a la variable E y el contenido a la var C.

Por ejemplo, para hacer lo que querías hacer:

while rdom; do
    if [[ $E = title ]]; then
        echo $C
        exit
    fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt
Yuzem avatar Apr 09 '2010 14:04 Yuzem

Puede utilizar la utilidad xpath. Se instala con el paquete Perl XML-XPath.

Uso:

/usr/bin/xpath [filename] query

o XMLStarlet . Para instalarlo en opensuse use:

sudo zypper install xmlstarlet

o pruébalo cnf xmlen otras plataformas.

Grisha avatar Apr 24 '2012 15:04 Grisha

Esto es suficiente...

xpath xhtmlfile.xhtml '/html/head/title/text()' > titleOfXHTMLPage.txt
teknopaul avatar Jan 05 '2015 10:01 teknopaul