Come analizzare XML in Bash?


Risposte:


153

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_domottenere 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=tage 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=/tage 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_domfunzione) 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>&quot;0032a28286680abee71aed5d059c6a09&quot;</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 => &quot;0032a28286680abee71aed5d059c6a09&quot;
/ETag => 
Size => 1785
/Size => 
StorageClass => STANDARD
/StorageClass => 
/Contents => 

Quindi se scrivessimo un whileloop 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_domchiami 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


2
Se rendi IFS (il separatore del campo di input) globale, dovresti ripristinarlo al suo valore originale alla fine, ho modificato la risposta per averlo. In caso contrario, qualsiasi altra suddivisione dell'input effettuata successivamente nello script verrà incasinata. Sospetto che il motivo per cui local non funzioni per te sia perché stai usando bash in una modalità di compatibilità (come il tuo shbang è #! / Bin / sh) o è una versione antica di bash.
Chad,

30
Solo perché puoi scrivere il tuo parser, non significa che dovresti.
Stephen Niedzielski,

1
@chad dice certamente qualcosa sul flusso di lavoro / implementazione di AWS che stavo cercando una risposta a "bash xml" per guadagnare anche il contenuto di un bucket S3!
Alastair,

2
@Alastair vedi github.com/chad3814/s3scripts per una serie di script bash che usiamo per manipolare oggetti S3
chad,

5
L'assegnazione dell'IFS in una variabile locale è fragile e non necessaria. Basta fare IFS=\< read ...:, che imposterà IFS solo per la chiamata in lettura. (Si noti che non approvo in alcun modo la pratica dell'utilizzo readper analizzare XML, e credo che farlo sia irto di pericoli e dovrebbe essere evitato.)
William Pursell,

64

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

potresti approfondire questo? Scommetto che è perfettamente chiaro per te .. e questa potrebbe essere un'ottima risposta - se potessi dire cosa stavi facendo lì .. puoi analizzarlo un po 'di più, magari generando un output di esempio?
Alex Gray,

1
Credete all'originale: questo one-liner è così elegante e sorprendente.
Maverick,

1
ottimo hack, ma ho dovuto usare virgolette doppie come echo "$ C" per impedire l'espansione della shell e la corretta interpretazione delle linee di fondo (dipende dalla codifica)
user311174

8
L'analisi di XML con grep e awk non va bene . Potrebbe essere un compromesso accettabile se gli XML sono abbastanza semplici e non hai troppo tempo, ma non può mai essere definita una buona soluzione.
Peter - Ripristina Monica il

59

Gli strumenti da riga di comando che possono essere richiamati dagli script della shell includono:

  • 4xpath : wrapper della riga di comando attorno al pacchetto 4Suite di Python
  • xmlstarlet
  • xpath - wrapper della riga di comando attorno alla libreria XPath di Perl
  • Xidel : funziona con URL e file. Funziona anche con JSON

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.


2
Da dove posso scaricare 'xpath' o '4xpath'?
Opher,

3
sì, un secondo voto / richiesta: dove scaricare quegli strumenti o intendi scrivere manualmente un wrapper? Preferirei non perdere tempo a farlo se non necessario.
David,

4
sudo apt-get install libxml-xpath-perl
Andrew Wagner,

22

È 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 xmlsu altre piattaforme.


5
L'uso di xml starlet è sicuramente un'opzione migliore rispetto alla scrittura del proprio serializzatore (come suggerito nelle altre risposte).
Bruno von Paris,

Su molti sistemi, ciò xpathche viene preinstallato non è adatto per l'uso come componente negli script. Vedere ad esempio stackoverflow.com/questions/15461737/… per un'elaborazione.
Tripleee,

2
Su Ubuntu / Debianapt-get install xmlstarlet
rubo77



5

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:

  • Websphere xml (attributi xmi e xmlns)
  • deve avere un terminale compatibile con 256 colori
  • 24 tonalità di grigio
  • aggiunta compatibilità per IBM AIX bash 3.2.16 (1)

Le funzioni, in primo luogo è xml_read_dom che viene chiamato ricorsivamente da xml_read:

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; }

colorazione:

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"'

Come caricare tutta quella roba:

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.

Come funziona:

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


Sto cercando di utilizzare le due funzioni precedenti che producono quanto segue ./read_xml.sh: line 22: (-1): substring expression < 0:?
Khmarbaise,

Linea 22:[ "x${ATTRIBUTES:(-1):1}x" == "x?x" ] ...
Khmarbaise,

scusa khmarbaise, queste sono funzioni della shell bash. Se vuoi adattarli come script di shell, devi sicuramente aspettarti alcuni adattamenti minori! Anche le funzioni aggiornate gestiscono i tuoi errori;)
scavenger

4

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_grepdove probabilmente scriveresti quello che vuoi xml_grep -t '/html/head/title' xhtmlfile.xhtml > titleOfXHTMLPage.txt(l' -topzione ti dà il risultato come testo anziché xml)


4

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 $titleil titolo e $imgcountil numero di immagini nel file, che dovrebbe essere flessibile come analizzarlo direttamente in bash.


Questo è esattamente quello di cui avevo bisogno! :)
Thomas Daugaard,

2

Bene, puoi usare l'utility xpath. Immagino che l'XML di perl :: Xpath lo contenga.


2

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:


2

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 lxmlper 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.

Esempio 1

#!/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))

lxmlpuò essere installato con pip install lxml. Su Ubuntu puoi usare sudo apt install python-lxml.

uso

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 il pprefisso (fornito da 'hack') nelle tue espressioni, ad es. //p:modulePer ottenere i moduli da un pom.xmlfile. Nel caso in cui il pprefisso sia già mappato nel tuo XML, dovrai modificare lo script per utilizzare un altro prefisso.


Esempio 2

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)

Questo è fantastico quando vuoi evitare di installare pacchetti extra o non hai accesso. Su una macchina build, posso giustificare un pip installover apt-geto yumcall extra . Grazie!
E. Moffat,

0

Il metodo di Yuzem può essere migliorato invertendo l'ordine dei segni <e >nella rdomfunzione 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 whileciclo.


0

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

-1

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"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.