¿Cómo analizar XML en Bash?
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
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_dom
obtener 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=tag
y 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=/tag
y 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_dom
funció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>"0032a28286680abee71aed5d059c6a09"</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 => "0032a28286680abee71aed5d059c6a09"
/ETag =>
Size => 1785
/Size =>
StorageClass => STANDARD
/StorageClass =>
/Contents =>
Entonces, si escribiéramos un while
bucle 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_dom
llamas 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.
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.
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
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 xml
en otras plataformas.
Esto es suficiente...
xpath xhtmlfile.xhtml '/html/head/title/text()' > titleOfXHTMLPage.txt