Idealmente, ciò che vorrei poter fare è:
cat xhtmlfile.xhtml |
getElementViaXPath --path='/html/head/title' |
sed -e 's%(^<title>|</title>$)%%g' > titleOfXHTMLPage.txt
Idealmente, ciò che vorrei poter fare è:
cat xhtmlfile.xhtml |
getElementViaXPath --path='/html/head/title' |
sed -e 's%(^<title>|</title>$)%%g' > titleOfXHTMLPage.txt
Risposte:
Questa è in realtà solo una spiegazione della risposta di Yuzem , ma non pensavo che così tante modifiche avrebbero dovuto essere fatte a qualcun altro, e i commenti non consentono la formattazione, quindi ...
rdom () { local IFS=\> ; read -d \< E C ;}
Chiamiamo "read_dom" invece di "rdom", spaziamo un po 'e usiamo variabili più lunghe:
read_dom () {
local IFS=\>
read -d \< ENTITY CONTENT
}
Va bene così definisce una funzione chiamata read_dom. La prima riga rende IFS (il separatore del campo di input) locale per questa funzione e la modifica in>. Ciò significa che quando leggi i dati anziché essere divisi automaticamente su spazio, tab o newline, questi vengono divisi su '>'. La riga successiva dice di leggere l'input da stdin e invece di fermarsi su una nuova riga, fermati quando vedi un carattere '<' (la -d per il delimitatore). Ciò che viene letto viene quindi diviso utilizzando l'IFS e assegnato alla variabile ENTITY e CONTENT. Quindi prendi quanto segue:
<tag>value</tag>
La prima chiamata per read_dom
ottenere una stringa vuota (poiché "<" è il primo carattere). Ciò viene diviso da IFS in solo '', poiché non esiste un carattere '>'. Leggi quindi assegna una stringa vuota a entrambe le variabili. La seconda chiamata ottiene la stringa 'tag> valore'. Ciò viene quindi diviso dall'IFS nei due campi "tag" e "valore". Leggi quindi assegna le variabili come: ENTITY=tag
e CONTENT=value
. La terza chiamata ottiene la stringa '/ tag>'. Ciò viene diviso dall'IFS nei due campi '/ tag' e ''. Leggi quindi assegna le variabili come: ENTITY=/tag
e CONTENT=
. La quarta chiamata restituirà uno stato diverso da zero perché abbiamo raggiunto la fine del file.
Ora il suo ciclo while ha ripulito un po 'per abbinare quanto sopra:
while read_dom; do
if [[ $ENTITY = "title" ]]; then
echo $CONTENT
exit
fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt
La prima riga dice semplicemente "mentre la funzione read_dom restituisce uno stato zero, procedi come segue". La seconda riga controlla se l'entità che abbiamo appena visto è "titolo". La riga successiva riprende il contenuto del tag. Le quattro linee escono. Se non era l'entità titolo, il ciclo si ripete sulla sesta riga. Reindirizziamo "xhtmlfile.xhtml" nell'input standard (per la read_dom
funzione) e reindirizziamo l'output standard su "titleOfXHTMLPage.txt" (l'eco di prima nel loop).
Ora dato quanto segue (simile a quello che si ottiene elencando un bucket su S3) per 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>
e il seguente ciclo:
while read_dom; do
echo "$ENTITY => $CONTENT"
done < input.xml
Dovresti ricevere:
=>
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 =>
Quindi se scrivessimo un while
loop come quello di Yuzem:
while read_dom; do
if [[ $ENTITY = "Key" ]] ; then
echo $CONTENT
fi
done < input.xml
Otterremmo un elenco di tutti i file nel bucket S3.
MODIFICA
Se per qualche motivo local IFS=\>
non funziona per te e lo imposti a livello globale, dovresti ripristinarlo alla fine della funzione come:
read_dom () {
ORIGINAL_IFS=$IFS
IFS=\>
read -d \< ENTITY CONTENT
IFS=$ORIGINAL_IFS
}
Altrimenti, qualsiasi linea di divisione che farai successivamente nello script verrà incasinata.
MODIFICA 2
Per dividere le coppie nome / valore dell'attributo puoi aumentare in questo read_dom()
modo:
read_dom () {
local IFS=\>
read -d \< ENTITY CONTENT
local ret=$?
TAG_NAME=${ENTITY%% *}
ATTRIBUTES=${ENTITY#* }
return $ret
}
Quindi scrivi la tua funzione per analizzare e ottenere i dati desiderati in questo modo:
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
}
Quindi mentre read_dom
chiami parse_dom
:
while read_dom; do
parse_dom
done
Quindi dato il seguente esempio di markup:
<example>
<bar size="bar_size" type="metal">bars content</bar>
<foo size="1789" type="unknown">foos content</foo>
</example>
Dovresti ottenere questo risultato:
$ cat example.xml | ./bash_xml.sh
bar type is: metal
foo size is: 1789
EDIT 3 un altro utente ha dichiarato di avere problemi con FreeBSD e ha suggerito di salvare lo stato di uscita dalla lettura e di restituirlo alla fine di read_dom come:
read_dom () {
local IFS=\>
read -d \< ENTITY CONTENT
local RET=$?
TAG_NAME=${ENTITY%% *}
ATTRIBUTES=${ENTITY#* }
return $RET
}
Non vedo alcun motivo per cui ciò non dovrebbe funzionare
IFS=\< read ...
:, che imposterà IFS solo per la chiamata in lettura. (Si noti che non approvo in alcun modo la pratica dell'utilizzo read
per analizzare XML, e credo che farlo sia irto di pericoli e dovrebbe essere evitato.)
Puoi farlo molto facilmente usando solo bash. Devi solo aggiungere questa funzione:
rdom () { local IFS=\> ; read -d \< E C ;}
Ora puoi usare rdom come read ma per documenti html. Quando chiamato rdom assegnerà l'elemento alla variabile E e il contenuto alla var C.
Ad esempio, per fare quello che volevi fare:
while rdom; do
if [[ $E = title ]]; then
echo $C
exit
fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt
Gli strumenti da riga di comando che possono essere richiamati dagli script della shell includono:
Uso anche xmllint e xsltproc con piccoli script di trasformazione XSL per eseguire l'elaborazione XML dalla riga di comando o negli script di shell.
È possibile utilizzare l'utilità xpath. È installato con il pacchetto Perl XML-XPath.
Uso:
/usr/bin/xpath [filename] query
o XMLStarlet . Per installarlo su opensuse usare:
sudo zypper install xmlstarlet
o prova cnf xml
su altre piattaforme.
xpath
che viene preinstallato non è adatto per l'uso come componente negli script. Vedere ad esempio stackoverflow.com/questions/15461737/… per un'elaborazione.
apt-get install xmlstarlet
Questo è sufficiente ...
xpath xhtmlfile.xhtml '/html/head/title/text()' > titleOfXHTMLPage.txt
apt-get install libxml-xpath-perl
.
Dai un'occhiata a XML2 da http://www.ofb.net/~egnor/xml2/ che converte XML in un formato orientato alla linea.
partendo dalla risposta del chad, ecco la soluzione di lavoro COMPLETA per analizzare UML, con gestione propper dei commenti, con solo 2 piccole funzioni (più di 2 bu puoi mescolarle tutte). Non dico che quello di Chad non abbia funzionato affatto, ma aveva troppi problemi con file XML mal formati: Quindi devi essere un po 'più complicato per gestire i commenti e gli spazi fuori posto / CR / TAB / ecc.
Lo scopo di questa risposta è di fornire funzioni bash pronte all'uso e pronte all'uso a chiunque abbia bisogno di analizzare UML senza strumenti complessi usando perl, python o altro. Per quanto mi riguarda, non riesco a installare cpan, né i moduli perl per il vecchio sistema operativo di produzione su cui sto lavorando e python non è disponibile.
Innanzitutto, una definizione delle parole UML utilizzate in questo post:
<!-- comment... -->
<tag attribute="value">content...</tag>
EDIT: funzioni aggiornate, con handle di:
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
}
e il secondo:
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
}
e infine le funzioni rtrim, trim ed 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; }
oh e avrai bisogno di alcune variabili dinamiche di colorazione pulite da definire inizialmente ed esportare anche:
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"'
O sai come creare funzioni e caricarle tramite FPATH (ksh) o un'emulazione di FPATH (bash)
In caso contrario, basta copiare / incollare tutto sulla riga di 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 la modalità Debug (-d) i commenti e gli attributi analizzati vengono stampati su stderr
./read_xml.sh: line 22: (-1): substring expression < 0
:?
[ "x${ATTRIBUTES:(-1):1}x" == "x?x" ] ...
Non sono a conoscenza di alcuno strumento di analisi XML della shell pura. Quindi molto probabilmente avrai bisogno di uno strumento scritto in un'altra lingua.
Il mio modulo XML :: Twig Perl viene fornito con un tale strumento:, xml_grep
dove probabilmente scriveresti quello che vuoi xml_grep -t '/html/head/title' xhtmlfile.xhtml > titleOfXHTMLPage.txt
(l' -t
opzione ti dà il risultato come testo anziché xml)
Un altro strumento da riga di comando è il mio nuovo Xidel . Supporta anche XPath 2 e XQuery, contrariamente al già citato xpath / xmlstarlet.
Il titolo può essere letto come:
xidel xhtmlfile.xhtml -e /html/head/title > titleOfXHTMLPage.txt
E ha anche una bella funzionalità per esportare più variabili su bash. Per esempio
eval $(xidel xhtmlfile.xhtml -e 'title := //title, imgcount := count(//img)' --output-format bash )
imposta $title
il titolo e $imgcount
il numero di immagini nel file, che dovrebbe essere flessibile come analizzarlo direttamente in bash.
Dopo alcune ricerche per la traduzione tra i formati Linux e Windows dei percorsi dei file in file XML, ho trovato interessanti tutorial e soluzioni su:
Sebbene ci siano alcune utility di console già pronte che potrebbero fare quello che vuoi, probabilmente ci vorrà meno tempo per scrivere un paio di righe di codice in un linguaggio di programmazione generico come Python che puoi facilmente estendere e adattare I tuoi bisogni.
Ecco uno script Python che utilizza lxml
per l'analisi: prende il nome di un file o un URL come primo parametro, un'espressione XPath come secondo parametro e stampa le stringhe / i nodi che corrispondono all'espressione data.
#!/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
può essere installato con pip install lxml
. Su Ubuntu puoi usare sudo apt install python-lxml
.
python xpath.py myfile.xml "//mynode"
lxml
accetta anche un URL come input:
python xpath.py http://www.feedforall.com/sample.xml "//link"
Nota : se il tuo XML ha uno spazio dei nomi predefinito senza prefisso (ad es.
xmlns=http://abc...
), Devi usare ilp
prefisso (fornito da 'hack') nelle tue espressioni, ad es.//p:module
Per ottenere i moduli da unpom.xml
file. Nel caso in cui ilp
prefisso sia già mappato nel tuo XML, dovrai modificare lo script per utilizzare un altro prefisso.
Uno script una tantum che serve allo scopo ristretto di estrarre i nomi dei moduli da un file apache maven. Nota come il nome del nodo ( module
) è preceduto dallo spazio dei nomi predefinito {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
over apt-get
o yum
call extra . Grazie!
Il metodo di Yuzem può essere migliorato invertendo l'ordine dei segni <
e >
nella rdom
funzione e le assegnazioni delle variabili, in modo che:
rdom () { local IFS=\> ; read -d \< E C ;}
diventa:
rdom () { local IFS=\< ; read -d \> C E ;}
Se l'analisi non viene eseguita in questo modo, l'ultimo tag nel file XML non viene mai raggiunto. Questo può essere problematico se si intende produrre un altro file XML alla fine del while
ciclo.
Funziona se desideri attributi 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
Mentre sembra che "non analizzare mai XML, JSON ... da bash senza uno strumento adeguato" è un buon consiglio, non sono d'accordo. Se questo è un lavoro secondario, è alla ricerca di uno strumento adeguato, quindi imparalo ... Awk può farlo in pochi minuti. I miei programmi devono funzionare su tutti i tipi di dati sopra menzionati. Cavolo, non voglio testare 30 strumenti per analizzare 5-7-10 formati diversi di cui ho bisogno se riesco a risolvere il problema in pochi minuti. Non mi interessa XML, JSON o altro! Ho bisogno di un'unica soluzione per tutti.
Ad esempio: il mio programma SmartHome gestisce le nostre case. Mentre lo fa, legge una pletora di dati in troppi formati diversi che non riesco a controllare. Non uso mai strumenti dedicati e adeguati poiché non voglio dedicare più di minuti alla lettura dei dati di cui ho bisogno. Con le regolazioni FS e RS, questa soluzione awk funziona perfettamente per qualsiasi formato testuale. Ma potrebbe non essere la risposta corretta quando il tuo compito principale è lavorare principalmente con un sacco di dati in quel formato!
Il problema di analizzare XML da bash ho affrontato ieri. Ecco come lo faccio per qualsiasi formato di dati gerarchico. Come bonus - Assegno i dati direttamente alle variabili in uno script bash.
Per semplificare la lettura, presenterò la soluzione in più fasi. Dai dati del test OP, ho creato un file: test.xml
Analizzare detto XML in bash ed estrarre i dati in 90 caratteri:
awk 'BEGIN { FS="<|>"; RS="\n" }; /host|username|password|dbname/ { print $2, $4 }' test.xml
Di solito uso una versione più leggibile poiché è più facile modificarla nella vita reale poiché spesso devo testare diversamente:
awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2,$4}' test.xml
Non mi interessa come viene chiamato il formato. Cerco solo la soluzione più semplice. In questo caso particolare, posso vedere dai dati che newline è il record separator (RS) e <> delimit fields (FS). Nel mio caso originale, ho avuto un'indicizzazione complicata di 6 valori all'interno di due record, mettendoli in relazione, trovando quando i dati esistono più campi (record) possono o meno esistere. Sono state necessarie 4 righe di awk per risolvere perfettamente il problema. Quindi, adatta l'idea ad ogni esigenza prima di usarla!
La seconda parte osserva semplicemente che c'è una stringa desiderata in una riga (RS) e, in tal caso, stampa i campi necessari (FS). Quanto sopra mi ha impiegato circa 30 secondi per copiare e adattare dall'ultimo comando che ho usato in questo modo (4 volte più a lungo). E questo è tutto! Fatto in 90 caratteri.
Ma ho sempre bisogno di inserire i dati ordinatamente in variabili nel mio script. Prima collaudo i costrutti in questo modo:
awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2"=\""$4"\"" }' test.xml
In alcuni casi uso printf anziché print. Quando vedo che tutto sembra a posto, finisco semplicemente di assegnare valori alle variabili. So che molti pensano che "eval" sia "malvagio", non c'è bisogno di commentare :) Trucco funziona perfettamente su tutte e quattro le mie reti per anni. Ma continua a imparare se non capisci perché questa potrebbe essere una cattiva pratica! Includendo assegnazioni di variabili bash e ampia spaziatura, la mia soluzione ha bisogno di 120 caratteri per fare tutto.
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"