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
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
Respuestas:
Esto es realmente solo una explicación de la respuesta de Yuzem , pero no sentí que esta edición se deba hacer a otra persona, y los comentarios no permiten el formateo, así que ...
rdom () { local IFS=\> ; read -d \< E C ;}
Llamemos a eso "read_dom" en lugar de "rdom", espacie un poco y use 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 a esta función y lo cambia a>. Eso significa que cuando lee datos en lugar de dividirse automáticamente en el espacio, la pestaña o las nuevas líneas, se divide en '>'. La siguiente línea dice que lea la entrada de stdin, y en lugar de detenerse en una nueva línea, deténgase cuando vea un carácter '<' (la -d para el indicador delimitador). Lo que se lee se divide usando el IFS y se asigna a la variable 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). Eso se divide por IFS en solo '', ya que no hay un carácter '>'. Leer luego asigna una cadena vacía a ambas variables. La segunda llamada obtiene la cadena 'etiqueta> valor'. Eso se divide luego por el IFS en los dos campos 'etiqueta' y 'valor'. Leer 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 '/ etiqueta' y ''. Leer 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 ciclo 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 dice: "mientras la función read_dom devuelve un estado cero, haga lo siguiente". La segunda línea verifica si la entidad que acabamos de ver es "título". La siguiente línea muestra el contenido de la etiqueta. Las cuatro salidas de línea. 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 elread_dom
función) y redirigimos la salida estándar a "titleOfXHTMLPage.txt" (el eco de antes en el bucle).
Ahora se da lo siguiente (similar a lo que obtienes al incluir un cubo en S3) para input.xml
:
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Name>sth-items</Name>
<IsTruncated>false</IsTruncated>
<Contents>
<Key>item-apple-iso@2x.png</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 => item-apple-iso@2x.png
/Key =>
LastModified => 2011-07-25T22:23:04.000Z
/LastModified =>
ETag => "0032a28286680abee71aed5d059c6a09"
/ETag =>
Size => 1785
/Size =>
StorageClass => STANDARD
/StorageClass =>
/Contents =>
Entonces, si escribimos 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 bucket de 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 haga más adelante en el script se verá afectada.
EDITAR 2
Para dividir los pares de nombre / valor de atributo, puede aumentar el read_dom()
tipo así:
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 dado el siguiente marcado de ejemplo:
<example>
<bar size="bar_size" type="metal">bars content</bar>
<foo size="1789" type="unknown">foos content</foo>
</example>
Deberías obtener esta salida:
$ cat example.xml | ./bash_xml.sh
bar type is: metal
foo size is: 1789
EDITAR 3 otro usuario dijo que estaban 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
IFS=\< read ...
que solo establecerá IFS para la llamada de lectura. (Tenga en cuenta que de ninguna manera estoy respaldando la práctica de usar read
para analizar xml, y creo que hacerlo está lleno de peligros y debería evitarse).
Puedes hacerlo fácilmente usando solo bash. Solo tiene que agregar esta función:
rdom () { local IFS=\> ; read -d \< E C ;}
Ahora puede usar rdom como read pero para documentos html. Cuando se llama rdom asignará el elemento a la variable E y el contenido a la variable C.
Por ejemplo, para hacer lo que quería hacer:
while rdom; do
if [[ $E = title ]]; then
echo $C
exit
fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt
Las herramientas de línea de comandos que se pueden llamar desde scripts de shell incluyen:
También uso xmllint y xsltproc con pequeños scripts de transformación XSL para hacer el procesamiento XML desde la línea de comandos o en scripts de shell.
Puede usar 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 prueba cnf xml
en otras plataformas.
xpath
que viene preinstalado no es adecuado para su uso como componente en scripts. Consulte, por ejemplo, stackoverflow.com/questions/15461737/… para obtener más detalles.
apt-get install xmlstarlet
Esto es suficiente...
xpath xhtmlfile.xhtml '/html/head/title/text()' > titleOfXHTMLPage.txt
apt-get install libxml-xpath-perl
.
Consulte XML2 desde http://www.ofb.net/~egnor/xml2/ que convierte XML a un formato orientado a líneas.
a partir de la respuesta de chad, aquí está la solución de trabajo COMPLETA para analizar UML, con manejo adecuado de comentarios, con solo 2 pequeñas funciones (más de 2 pero puede mezclarlas todas). No digo que el de Chad no funcionó en absoluto, pero tenía demasiados problemas con los archivos XML mal formateados: por lo tanto, debe ser un poco más complicado para manejar los comentarios y espacios mal ubicados / CR / TAB / etc.
El propósito de esta respuesta es proporcionar funciones de bash listas para usar, listas para usar, a cualquiera que necesite analizar UML sin herramientas complejas que usen perl, python o cualquier otra cosa. En cuanto a mí, no puedo instalar cpan, ni módulos perl para el antiguo sistema operativo de producción en el que estoy trabajando, y python no está disponible.
Primero, una definición de las palabras UML utilizadas en esta publicación:
<!-- comment... -->
<tag attribute="value">content...</tag>
EDITAR: funciones actualizadas, con identificador de:
xml_read_dom() {
# /programming/893585/how-to-parse-xml-in-bash
local ENTITY IFS=\>
if $ITSACOMMENT; then
read -d \< COMMENTS
COMMENTS="$(rtrim "${COMMENTS}")"
return 0
else
read -d \< ENTITY CONTENT
CR=$?
[ "x${ENTITY:0:1}x" == "x/x" ] && return 0
TAG_NAME=${ENTITY%%[[:space:]]*}
[ "x${TAG_NAME}x" == "x?xmlx" ] && TAG_NAME=xml
TAG_NAME=${TAG_NAME%%:*}
ATTRIBUTES=${ENTITY#*[[:space:]]}
ATTRIBUTES="${ATTRIBUTES//xmi:/}"
ATTRIBUTES="${ATTRIBUTES//xmlns:/}"
fi
# when comments sticks to !-- :
[ "x${TAG_NAME:0:3}x" == "x!--x" ] && COMMENTS="${TAG_NAME:3} ${ATTRIBUTES}" && ITSACOMMENT=true && return 0
# http://tldp.org/LDP/abs/html/string-manipulation.html
# INFO: oh wait it doesn't work on IBM AIX bash 3.2.16(1):
# [ "x${ATTRIBUTES:(-1):1}x" == "x/x" -o "x${ATTRIBUTES:(-1):1}x" == "x?x" ] && ATTRIBUTES="${ATTRIBUTES:0:(-1)}"
[ "x${ATTRIBUTES:${#ATTRIBUTES} -1:1}x" == "x/x" -o "x${ATTRIBUTES:${#ATTRIBUTES} -1:1}x" == "x?x" ] && ATTRIBUTES="${ATTRIBUTES:0:${#ATTRIBUTES} -1}"
return $CR
}
y el segundo:
xml_read() {
# /programming/893585/how-to-parse-xml-in-bash
ITSACOMMENT=false
local MULTIPLE_ATTR LIGHT FORCE_PRINT XAPPLY XCOMMAND XATTRIBUTE GETCONTENT fileXml tag attributes attribute tag2print TAGPRINTED attribute2print XAPPLIED_COLOR PROSTPROCESS USAGE
local TMP LOG LOGG
LIGHT=false
FORCE_PRINT=false
XAPPLY=false
MULTIPLE_ATTR=false
XAPPLIED_COLOR=g
TAGPRINTED=false
GETCONTENT=false
PROSTPROCESS=cat
Debug=${Debug:-false}
TMP=/tmp/xml_read.$RANDOM
USAGE="${C}${FUNCNAME}${c} [-cdlp] [-x command <-a attribute>] <file.xml> [tag | \"any\"] [attributes .. | \"content\"]
${nn[2]} -c = NOCOLOR${END}
${nn[2]} -d = Debug${END}
${nn[2]} -l = LIGHT (no \"attribute=\" printed)${END}
${nn[2]} -p = FORCE PRINT (when no attributes given)${END}
${nn[2]} -x = apply a command on an attribute and print the result instead of the former value, in green color${END}
${nn[1]} (no attribute given will load their values into your shell; use '-p' to print them as well)${END}"
! (($#)) && echo2 "$USAGE" && return 99
(( $# < 2 )) && ERROR nbaram 2 0 && return 99
# getopts:
while getopts :cdlpx:a: _OPT 2>/dev/null
do
{
case ${_OPT} in
c) PROSTPROCESS="${DECOLORIZE}" ;;
d) local Debug=true ;;
l) LIGHT=true; XAPPLIED_COLOR=END ;;
p) FORCE_PRINT=true ;;
x) XAPPLY=true; XCOMMAND="${OPTARG}" ;;
a) XATTRIBUTE="${OPTARG}" ;;
*) _NOARGS="${_NOARGS}${_NOARGS+, }-${OPTARG}" ;;
esac
}
done
shift $((OPTIND - 1))
unset _OPT OPTARG OPTIND
[ "X${_NOARGS}" != "X" ] && ERROR param "${_NOARGS}" 0
fileXml=$1
tag=$2
(( $# > 2 )) && shift 2 && attributes=$*
(( $# > 1 )) && MULTIPLE_ATTR=true
[ -d "${fileXml}" -o ! -s "${fileXml}" ] && ERROR empty "${fileXml}" 0 && return 1
$XAPPLY && $MULTIPLE_ATTR && [ -z "${XATTRIBUTE}" ] && ERROR param "-x command " 0 && return 2
# nb attributes == 1 because $MULTIPLE_ATTR is false
[ "${attributes}" == "content" ] && GETCONTENT=true
while xml_read_dom; do
# (( CR != 0 )) && break
(( PIPESTATUS[1] != 0 )) && break
if $ITSACOMMENT; then
# oh wait it doesn't work on IBM AIX bash 3.2.16(1):
# if [ "x${COMMENTS:(-2):2}x" == "x--x" ]; then COMMENTS="${COMMENTS:0:(-2)}" && ITSACOMMENT=false
# elif [ "x${COMMENTS:(-3):3}x" == "x-->x" ]; then COMMENTS="${COMMENTS:0:(-3)}" && ITSACOMMENT=false
if [ "x${COMMENTS:${#COMMENTS} - 2:2}x" == "x--x" ]; then COMMENTS="${COMMENTS:0:${#COMMENTS} - 2}" && ITSACOMMENT=false
elif [ "x${COMMENTS:${#COMMENTS} - 3:3}x" == "x-->x" ]; then COMMENTS="${COMMENTS:0:${#COMMENTS} - 3}" && ITSACOMMENT=false
fi
$Debug && echo2 "${N}${COMMENTS}${END}"
elif test "${TAG_NAME}"; then
if [ "x${TAG_NAME}x" == "x${tag}x" -o "x${tag}x" == "xanyx" ]; then
if $GETCONTENT; then
CONTENT="$(trim "${CONTENT}")"
test ${CONTENT} && echo "${CONTENT}"
else
# eval local $ATTRIBUTES => eval test "\"\$${attribute}\"" will be true for matching attributes
eval local $ATTRIBUTES
$Debug && (echo2 "${m}${TAG_NAME}: ${M}$ATTRIBUTES${END}"; test ${CONTENT} && echo2 "${m}CONTENT=${M}$CONTENT${END}")
if test "${attributes}"; then
if $MULTIPLE_ATTR; then
# we don't print "tag: attr=x ..." for a tag passed as argument: it's usefull only for "any" tags so then we print the matching tags found
! $LIGHT && [ "x${tag}x" == "xanyx" ] && tag2print="${g6}${TAG_NAME}: "
for attribute in ${attributes}; do
! $LIGHT && attribute2print="${g10}${attribute}${g6}=${g14}"
if eval test "\"\$${attribute}\""; then
test "${tag2print}" && ${print} "${tag2print}"
TAGPRINTED=true; unset tag2print
if [ "$XAPPLY" == "true" -a "${attribute}" == "${XATTRIBUTE}" ]; then
eval ${print} "%s%s\ " "\${attribute2print}" "\${${XAPPLIED_COLOR}}\"\$(\$XCOMMAND \$${attribute})\"\${END}" && eval unset ${attribute}
else
eval ${print} "%s%s\ " "\${attribute2print}" "\"\$${attribute}\"" && eval unset ${attribute}
fi
fi
done
# this trick prints a CR only if attributes have been printed durint the loop:
$TAGPRINTED && ${print} "\n" && TAGPRINTED=false
else
if eval test "\"\$${attributes}\""; then
if $XAPPLY; then
eval echo "\${g}\$(\$XCOMMAND \$${attributes})" && eval unset ${attributes}
else
eval echo "\$${attributes}" && eval unset ${attributes}
fi
fi
fi
else
echo eval $ATTRIBUTES >>$TMP
fi
fi
fi
fi
unset CR TAG_NAME ATTRIBUTES CONTENT COMMENTS
done < "${fileXml}" | ${PROSTPROCESS}
# http://mywiki.wooledge.org/BashFAQ/024
# INFO: I set variables in a "while loop" that's in a pipeline. Why do they disappear? workaround:
if [ -s "$TMP" ]; then
$FORCE_PRINT && ! $LIGHT && cat $TMP
# $FORCE_PRINT && $LIGHT && perl -pe 's/[[:space:]].*?=/ /g' $TMP
$FORCE_PRINT && $LIGHT && sed -r 's/[^\"]*([\"][^\"]*[\"][,]?)[^\"]*/\1 /g' $TMP
. $TMP
rm -f $TMP
fi
unset ITSACOMMENT
}
y, por último, las funciones rtrim, trim y echo2 (to stderr):
rtrim() {
local var=$@
var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters
echo -n "$var"
}
trim() {
local var=$@
var="${var#"${var%%[![:space:]]*}"}" # remove leading whitespace characters
var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters
echo -n "$var"
}
echo2() { echo -e "$@" 1>&2; }
ah, y necesitará algunas variables dinámicas de colorización definidas al principio, y también exportadas:
set -a
TERM=xterm-256color
case ${UNAME} in
AIX|SunOS)
M=$(${print} '\033[1;35m')
m=$(${print} '\033[0;35m')
END=$(${print} '\033[0m')
;;
*)
m=$(tput setaf 5)
M=$(tput setaf 13)
# END=$(tput sgr0) # issue on Linux: it can produces ^[(B instead of ^[[0m, more likely when using screenrc
END=$(${print} '\033[0m')
;;
esac
# 24 shades of grey:
for i in $(seq 0 23); do eval g$i="$(${print} \"\\033\[38\;5\;$((232 + i))m\")" ; done
# another way of having an array of 5 shades of grey:
declare -a colorNums=(238 240 243 248 254)
for num in 0 1 2 3 4; do nn[$num]=$(${print} "\033[38;5;${colorNums[$num]}m"); NN[$num]=$(${print} "\033[48;5;${colorNums[$num]}m"); done
# piped decolorization:
DECOLORIZE='eval sed "s,${END}\[[0-9;]*[m|K],,g"'
Ya sabes cómo crear funciones y cargarlas a través de FPATH (ksh) o una emulación de FPATH (bash)
Si no, simplemente copie / pegue todo en la línea de comando.
xml_read [-cdlp] [-x command <-a attribute>] <file.xml> [tag | "any"] [attributes .. | "content"]
-c = NOCOLOR
-d = Debug
-l = LIGHT (no \"attribute=\" printed)
-p = FORCE PRINT (when no attributes given)
-x = apply a command on an attribute and print the result instead of the former value, in green color
(no attribute given will load their values into your shell as $ATTRIBUTE=value; use '-p' to print them as well)
xml_read server.xml title content # print content between <title></title>
xml_read server.xml Connector port # print all port values from Connector tags
xml_read server.xml any port # print all port values from any tags
Con el modo de depuración (-d), los comentarios y los atributos analizados se imprimen en stderr
./read_xml.sh: line 22: (-1): substring expression < 0
?
[ "x${ATTRIBUTES:(-1):1}x" == "x?x" ] ...
No conozco ninguna herramienta de análisis XML de shell puro. Por lo tanto, lo más probable es que necesite una herramienta escrita en otro idioma.
Mi módulo XML :: Twig Perl viene con una herramienta de este tipo: xml_grep
donde probablemente escribirías lo que quieras xml_grep -t '/html/head/title' xhtmlfile.xhtml > titleOfXHTMLPage.txt
(la -t
opción te da el resultado como texto en lugar de xml)
Otra herramienta de línea de comando es mi nuevo Xidel . También es compatible con XPath 2 y XQuery, a diferencia del ya mencionado xpath / xmlstarlet.
El título se puede leer como:
xidel xhtmlfile.xhtml -e /html/head/title > titleOfXHTMLPage.txt
Y también tiene una característica genial para exportar múltiples variables a bash. Por ejemplo
eval $(xidel xhtmlfile.xhtml -e 'title := //title, imgcount := count(//img)' --output-format bash )
establece $title
el título y $imgcount
la cantidad de imágenes en el archivo, que debería ser tan flexible como analizarlo directamente en bash.
Después de algunas investigaciones para la traducción entre los formatos Linux y Windows de las rutas de archivos en archivos XML, encontré tutoriales y soluciones interesantes sobre:
Si bien hay bastantes utilidades de consola listas para usar que pueden hacer lo que desea, probablemente tomará menos tiempo escribir un par de líneas de código en un lenguaje de programación de propósito general como Python, que puede ampliar y adaptar fácilmente tus necesidades.
Aquí hay un script de Python que usa lxml
para analizar: toma el nombre de un archivo o una URL como primer parámetro, una expresión XPath como segundo parámetro e imprime las cadenas / nodos que coinciden con la expresión dada.
#!/usr/bin/env python
import sys
from lxml import etree
tree = etree.parse(sys.argv[1])
xpath_expression = sys.argv[2]
# a hack allowing to access the
# default namespace (if defined) via the 'p:' prefix
# E.g. given a default namespaces such as 'xmlns="http://maven.apache.org/POM/4.0.0"'
# an XPath of '//p:module' will return all the 'module' nodes
ns = tree.getroot().nsmap
if ns.keys() and None in ns:
ns['p'] = ns.pop(None)
# end of hack
for e in tree.xpath(xpath_expression, namespaces=ns):
if isinstance(e, str):
print(e)
else:
print(e.text and e.text.strip() or etree.tostring(e, pretty_print=True))
lxml
Se puede instalar con pip install lxml
. En ubuntu puedes usar sudo apt install python-lxml
.
python xpath.py myfile.xml "//mynode"
lxml
También acepta una URL como entrada:
python xpath.py http://www.feedforall.com/sample.xml "//link"
Nota : Si su XML tiene un espacio de nombre predeterminado sin prefijo (p
xmlns=http://abc...
. Ej. ), Entonces debe usar elp
prefijo (proporcionado por el 'pirateo') en sus expresiones, p. Ej.//p:module
Para obtener los módulos de unpom.xml
archivo. En caso de que elp
prefijo ya esté asignado en su XML, deberá modificar el script para usar otro prefijo.
Una secuencia de comandos única que tiene el propósito limitado de extraer nombres de módulos de un archivo apache maven. Observe cómo el nombre del nodo ( module
) tiene el prefijo con el espacio de nombres predeterminado{http://maven.apache.org/POM/4.0.0}
:
pom.xml :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modules>
<module>cherries</module>
<module>bananas</module>
<module>pears</module>
</modules>
</project>
module_extractor.py :
from lxml import etree
for _, e in etree.iterparse(open("pom.xml"), tag="{http://maven.apache.org/POM/4.0.0}module"):
print(e.text)
pip install
más apt-get
o yum
llamada. ¡Gracias!
El método de Yuzem se puede mejorar invirtiendo el orden de los signos <
y >
en la rdom
función y las asignaciones de variables, de modo que:
rdom () { local IFS=\> ; read -d \< E C ;}
se convierte en:
rdom () { local IFS=\< ; read -d \> C E ;}
Si el análisis no se realiza así, nunca se alcanza la última etiqueta en el archivo XML. Esto puede ser problemático si tiene la intención de generar otro archivo XML al final del while
ciclo.
Esto funciona si desea atributos XML:
$ cat alfa.xml
<video server="asdf.com" stream="H264_400.mp4" cdn="limelight"/>
$ sed 's.[^ ]*..;s./>..' alfa.xml > alfa.sh
$ . ./alfa.sh
$ echo "$stream"
H264_400.mp4
Si bien parece que "nunca analizar XML, JSON ... desde bash sin una herramienta adecuada" es un buen consejo, no estoy de acuerdo. Si este es un trabajo secundario, es fácil buscar la herramienta adecuada y luego aprenderla ... Awk puede hacerlo en minutos. Mis programas tienen que trabajar con todos los datos mencionados anteriormente y más tipos de datos. Demonios, no quiero probar 30 herramientas para analizar 5-7-10 formatos diferentes que necesito si puedo resolver el problema en minutos. ¡No me importa XML, JSON o lo que sea! Necesito una solución única para todos ellos.
Como ejemplo: mi programa SmartHome ejecuta nuestros hogares. Mientras lo hace, lee una gran cantidad de datos en demasiados formatos diferentes que no puedo controlar. Nunca uso herramientas adecuadas y dedicadas, ya que no quiero pasar más de minutos leyendo los datos que necesito. Con ajustes FS y RS, esta solución awk funciona perfectamente para cualquier formato de texto. ¡Pero puede que no sea la respuesta adecuada cuando su tarea principal es trabajar principalmente con un montón de datos en ese formato!
El problema de analizar XML desde bash me enfrenté ayer. Así es como lo hago para cualquier formato de datos jerárquico. Como beneficio adicional: asigno datos directamente a las variables en un script bash.
Para facilitar la lectura, presentaré la solución por etapas. A partir de los datos de prueba de OP, creé un archivo: test.xml
Analizar dicho XML en bash y extraer los datos en 90 caracteres:
awk 'BEGIN { FS="<|>"; RS="\n" }; /host|username|password|dbname/ { print $2, $4 }' test.xml
Normalmente uso una versión más legible ya que es más fácil de modificar en la vida real, ya que a menudo necesito probar de manera diferente:
awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2,$4}' test.xml
No me importa cómo se llama el formato. Busco solo la solución más simple. En este caso particular, puedo ver a partir de los datos que newline es el separador de registros (RS) y <> delimit fields (FS). En mi caso original, tuve una indexación complicada de 6 valores dentro de dos registros, relacionándolos, encontrar cuándo existen los datos más campos (registros) pueden o no existir. Se necesitaron 4 líneas de awk para resolver el problema perfectamente. ¡Adapte la idea a cada necesidad antes de usarla!
La segunda parte simplemente ve que hay una cadena deseada en una línea (RS) y, de ser así, imprime los campos necesarios (FS). Lo anterior me tomó unos 30 segundos para copiar y adaptar el último comando que usé de esta manera (4 veces más). ¡Y eso es todo! Hecho en 90 caracteres.
Pero, siempre necesito obtener los datos perfectamente en variables en mi script. Primero pruebo las construcciones así:
awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2"=\""$4"\"" }' test.xml
En algunos casos, uso printf en lugar de print. Cuando veo que todo se ve bien, simplemente termino de asignar valores a las variables. Sé que muchos piensan que "eval" es "malvado", no es necesario comentar :) El truco funciona perfectamente en mis cuatro redes durante años. ¡Pero sigue aprendiendo si no entiendes por qué esto puede ser una mala práctica! Incluyendo asignaciones de variables bash y un amplio espacio, mi solución necesita 120 caracteres para hacer todo.
eval $( awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2"=\""$4"\"" }' test.xml ); echo "host: $host, username: $username, password: $password dbname: $dbname"