Wie analysiere ich XML in Bash?


Antworten:


152

Dies ist wirklich nur eine Erklärung von Yuzems Antwort, aber ich hatte nicht das Gefühl, dass so viel an jemand anderem bearbeitet werden sollte, und Kommentare erlauben keine Formatierung, also ...

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

Nennen wir das "read_dom" anstelle von "rdom", platzieren Sie es ein wenig und verwenden Sie längere Variablen:

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

Okay, es definiert eine Funktion namens read_dom. In der ersten Zeile wird IFS (das Eingabefeldtrennzeichen) für diese Funktion lokalisiert und in> geändert. Das heißt, wenn Sie Daten lesen, anstatt sie automatisch auf Leerzeichen, Tabulatoren oder Zeilenumbrüche aufzuteilen, werden sie auf '>' aufgeteilt. In der nächsten Zeile wird angegeben, dass Eingaben von stdin gelesen werden sollen. Anstatt an einer neuen Zeile anzuhalten, halten Sie an, wenn Sie ein '<' (das -d für Deliminator-Flag) sehen. Was gelesen wird, wird dann mit dem IFS aufgeteilt und der Variablen ENTITY und CONTENT zugewiesen. Nehmen Sie also Folgendes:

<tag>value</tag>

Der erste Aufruf, read_domum eine leere Zeichenfolge zu erhalten (da das '<' das erste Zeichen ist). Das wird von IFS in nur '' aufgeteilt, da es kein '>' Zeichen gibt. Read weist dann beiden Variablen eine leere Zeichenfolge zu. Der zweite Aufruf erhält die Zeichenfolge 'tag> value'. Das wird dann vom IFS in die beiden Felder 'tag' und 'value' aufgeteilt. Read weist dann die Variablen wie folgt zu: ENTITY=tagund CONTENT=value. Der dritte Aufruf erhält die Zeichenfolge '/ tag>'. Das wird vom IFS in die beiden Felder '/ tag' und '' aufgeteilt. Read weist dann die Variablen wie folgt zu: ENTITY=/tagund CONTENT=. Der vierte Aufruf gibt einen Status ungleich Null zurück, da das Dateiende erreicht ist.

Jetzt hat seine while-Schleife ein wenig aufgeräumt, um dem oben genannten zu entsprechen:

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

In der ersten Zeile steht nur: "Während die Funktion read_dom den Status Null zurückgibt, gehen Sie wie folgt vor." In der zweiten Zeile wird überprüft, ob die Entität, die wir gerade gesehen haben, "Titel" ist. In der nächsten Zeile wird der Inhalt des Tags wiedergegeben. Die vier Zeilen werden beendet. Wenn es nicht die Titelentität war, wird die Schleife in der sechsten Zeile wiederholt. Wir leiten "xhtmlfile.xhtml" in die Standardeingabe (für die read_domFunktion) und die Standardausgabe in "titleOfXHTMLPage.txt" (das Echo von früher in der Schleife) um.

Geben Sie nun Folgendes an (ähnlich wie beim Auflisten eines Buckets in S3) für 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>

und die folgende Schleife:

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

Du solltest bekommen:

 => 
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 => 

Wenn wir also eine whileSchleife wie die von Yuzem geschrieben haben:

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

Wir würden eine Liste aller Dateien im S3-Bucket erhalten.

BEARBEITEN Wenn es aus irgendeinem Grund local IFS=\>bei Ihnen nicht funktioniert und Sie es global festlegen, sollten Sie es am Ende der Funktion wie folgt zurücksetzen:

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

Andernfalls wird jede Zeilenaufteilung, die Sie später im Skript vornehmen, durcheinander gebracht.

BEARBEITEN 2 Um Attributname / Wert-Paare aufzuteilen, können Sie Folgendes erweitern read_dom():

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

Schreiben Sie dann Ihre Funktion, um die gewünschten Daten zu analysieren und abzurufen:

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
}

Dann, während Sie read_domanrufen parse_dom:

while read_dom; do
    parse_dom
done

Geben Sie dann das folgende Beispiel-Markup an:

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

Sie sollten diese Ausgabe erhalten:

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

EDIT 3 Ein anderer Benutzer gab an, Probleme mit FreeBSD zu haben, und schlug vor, den Exit-Status vor dem Lesen zu speichern und am Ende von read_dom zurückzugeben.

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

Ich sehe keinen Grund, warum das nicht funktionieren sollte


2
Wenn Sie IFS (das Eingabefeldtrennzeichen) global machen, sollten Sie es am Ende auf seinen ursprünglichen Wert zurücksetzen. Ich habe die Antwort bearbeitet, um dies zu haben. Andernfalls wird jede andere Eingabeaufteilung, die Sie später in Ihrem Skript vornehmen, durcheinander gebracht. Ich vermute, der Grund, warum local für Sie nicht funktioniert, ist, dass Sie entweder bash in einem Kompatibilitätsmodus verwenden (wie Ihr shbang #! / Bin / sh ist) oder es ist eine alte Version von bash.
Chad

30
Nur weil Sie Ihren eigenen Parser schreiben können, heißt das nicht, dass Sie es sollten.
Stephen Niedzielski

1
@chad es sicherlich etwas sagt über AWS‘Workflow / Implementierung , dass ich nach einer Antwort auf‚bash xml‘war auf der Suche auch wget den Inhalt eines S3 Eimer!
Alastair

2
@Alastair siehe github.com/chad3814/s3scripts für eine Reihe von Bash-Skripten, die wir verwenden, um S3-Objekte zu manipulieren
chad

5
Das Zuweisen von IFS in einer lokalen Variablen ist fragil und nicht erforderlich. Tun Sie einfach : IFS=\< read ..., wodurch nur IFS für den Leseaufruf festgelegt wird. (Beachten Sie, dass ich die Praxis des readParsen von XML in keiner Weise befürworte , und ich glaube, dass dies mit Gefahren behaftet ist und vermieden werden sollte.)
William Pursell

64

Sie können dies sehr einfach mit nur Bash tun. Sie müssen nur diese Funktion hinzufügen:

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

Jetzt können Sie rdom wie read verwenden, aber für HTML-Dokumente. Beim Aufruf von rdom wird das Element der Variablen E und der Inhalt der Variable C zugewiesen.

Zum Beispiel, um das zu tun, was Sie tun wollten:

while rdom; do
    if [[ $E = title ]]; then
        echo $C
        exit
    fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt

Könnten Sie das näher erläutern? Ich würde wetten, dass es Ihnen vollkommen klar ist. Und dies könnte eine großartige Antwort sein. Wenn ich sagen könnte, was Sie dort gemacht haben. Können Sie es ein wenig weiter aufschlüsseln und möglicherweise eine Beispielausgabe generieren?
Alex Gray

1
Dank des Originals - dieser Einzeiler ist so verdammt elegant und erstaunlich.
Außenseiter

1
Toller Hack, aber ich musste doppelte Anführungszeichen wie Echo "$ C" verwenden, um die Shell-Erweiterung und die korrekte Interpretation der Endzeilen zu verhindern (abhängig von der Folge)
user311174

8
Das Parsen von XML mit grep und awk ist nicht in Ordnung . Es kann ein akzeptabler Kompromiss sein, wenn die XMLs einfach genug sind und Sie nicht zu viel Zeit haben, aber es kann nie als gute Lösung bezeichnet werden.
Peter

59

Zu den Befehlszeilentools, die über Shell-Skripte aufgerufen werden können, gehören:

  • 4xpath - Befehlszeilen-Wrapper um das 4Suite- Paket von Python
  • XMLStarlet
  • xpath - Befehlszeilen-Wrapper um Perls XPath-Bibliothek
  • Xidel - Funktioniert sowohl mit URLs als auch mit Dateien. Funktioniert auch mit JSON

Ich verwende auch xmllint und xsltproc mit kleinen XSL-Transformationsskripten, um die XML-Verarbeitung über die Befehlszeile oder in Shell-Skripten durchzuführen.


2
Wo kann ich 'xpath' oder '4xpath' herunterladen?
Opher

3
Ja, eine zweite Abstimmung / Anfrage - wo kann man diese Tools herunterladen, oder meinst du, man muss manuell einen Wrapper schreiben? Ich würde lieber keine Zeit damit verschwenden, es sei denn, dies ist notwendig.
David

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

22

Sie können das Dienstprogramm xpath verwenden. Es wird mit dem Perl XML-XPath-Paket installiert.

Verwendung:

/usr/bin/xpath [filename] query

oder XMLStarlet . Um es auf opensuse zu installieren, verwenden Sie:

sudo zypper install xmlstarlet

oder versuchen Sie es cnf xmlauf anderen Plattformen.


5
Die Verwendung von XML-Starlet ist definitiv eine bessere Option als das Schreiben eines eigenen Serializers (wie in den anderen Antworten vorgeschlagen).
Bruno von Paris

Auf vielen Systemen ist die xpathvorinstallierte Version nicht für die Verwendung als Komponente in Skripten geeignet. Siehe z. B. stackoverflow.com/questions/15461737/… für eine Ausarbeitung.
Tripleee

2
Auf Ubuntu / Debianapt-get install xmlstarlet
rubo77



5

Ausgehend von der Antwort des Chad ist hier die KOMPLETTE Arbeitslösung zum Parsen von UML mit korrekter Behandlung von Kommentaren mit nur 2 kleinen Funktionen (mehr als 2 bu können Sie alle mischen). Ich sage nicht, dass chads überhaupt nicht funktioniert hat, aber es gab zu viele Probleme mit schlecht formatierten XML-Dateien: Sie müssen also etwas kniffliger sein, um mit Kommentaren und falsch platzierten Leerzeichen / CR / TAB / etc. Zu umgehen.

Der Zweck dieser Antwort besteht darin, jedem, der UML ohne komplexe Tools mit Perl, Python oder etwas anderem analysieren muss, sofort einsatzbereite Bash-Funktionen zur Verfügung zu stellen. Ich kann weder cpan noch Perl-Module für das alte Produktionsbetriebssystem installieren, an dem ich arbeite, und Python ist nicht verfügbar.

Zunächst eine Definition der in diesem Beitrag verwendeten UML-Wörter:

<!-- comment... -->
<tag attribute="value">content...</tag>

EDIT: aktualisierte Funktionen, mit Handle von:

  • Websphere xml (Attribute xmi und xmlns)
  • muss ein kompatibles Terminal mit 256 Farben haben
  • 24 Graustufen
  • Kompatibilität für IBM AIX Bash 3.2.16 hinzugefügt (1)

Die Funktionen sind zunächst die xml_read_dom, die von xml_read rekursiv aufgerufen wird:

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
}

und der zweite:

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
}

und schließlich die Funktionen rtrim, trim und 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; }

Färbung:

Oh, und Sie müssen zunächst einige ordentliche kolorierende dynamische Variablen definieren und auch exportieren:

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

Wie man all das Zeug lädt:

Entweder wissen Sie, wie Sie Funktionen erstellen und über FPATH (ksh) oder eine Emulation von FPATH (bash) laden.

Wenn nicht, kopieren Sie einfach alles in die Befehlszeile.

Wie funktioniert es:

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

Im Debug-Modus (-d) werden Kommentare und analysierte Attribute auf stderr gedruckt


Ich versuche, die beiden oben genannten Funktionen zu verwenden, die Folgendes erzeugen : ./read_xml.sh: line 22: (-1): substring expression < 0?
Khmarbaise

Zeile 22:[ "x${ATTRIBUTES:(-1):1}x" == "x?x" ] ...
khmarbaise

sorry khmarbaise, das sind bash shell funktionen. Wenn Sie sie als Shell-Skripte anpassen möchten, müssen Sie mit einigen geringfügigen Anpassungen rechnen! Auch die aktualisierten Funktionen behandeln Ihre Fehler;)
Scavenger

4

Mir ist kein reines Shell-XML-Parsing-Tool bekannt. Sie benötigen also höchstwahrscheinlich ein Tool, das in einer anderen Sprache geschrieben ist.

Mein XML :: Twig Perl-Modul enthält ein solches Tool: xml_grepHier können Sie wahrscheinlich schreiben, als was Sie möchten xml_grep -t '/html/head/title' xhtmlfile.xhtml > titleOfXHTMLPage.txt(die -tOption gibt Ihnen das Ergebnis als Text anstelle von XML).


4

Ein weiteres Kommandozeilen-Tool ist mein neues Xidel . Es unterstützt auch XPath 2 und XQuery, im Gegensatz zu dem bereits erwähnten xpath / xmlstarlet.

Der Titel kann wie folgt gelesen werden:

xidel xhtmlfile.xhtml -e /html/head/title > titleOfXHTMLPage.txt

Und es hat auch eine coole Funktion, um mehrere Variablen nach Bash zu exportieren. Beispielsweise

eval $(xidel xhtmlfile.xhtml -e 'title := //title, imgcount := count(//img)' --output-format bash )

Legt $titleden Titel und $imgcountdie Anzahl der Bilder in der Datei fest, die so flexibel sein sollten wie das direkte Parsen in Bash.


Genau das brauchte ich! :)
Thomas Daugaard

2

Nun, Sie können das Dienstprogramm xpath verwenden. Ich denke, Perls XML :: Xpath enthält es.


2

Nach einigen Recherchen zur Übersetzung der Dateipfade in XML-Dateien zwischen Linux- und Windows-Formaten fand ich interessante Tutorials und Lösungen zu folgenden Themen:


2

Zwar gibt es einige vorgefertigte Konsolendienstprogramme, die möglicherweise das tun, was Sie möchten, aber es wird wahrscheinlich weniger Zeit in Anspruch nehmen, einige Codezeilen in einer universellen Programmiersprache wie Python zu schreiben, die Sie problemlos erweitern und anpassen können Deine Bedürfnisse.

Hier ist ein Python-Skript, das lxmlzum Parsen verwendet wird: Es verwendet den Namen einer Datei oder einer URL als ersten Parameter, einen XPath-Ausdruck als zweiten Parameter und druckt die Zeichenfolgen / Knoten, die dem angegebenen Ausdruck entsprechen.

Beispiel 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))

lxmlkann mit installiert werden pip install lxml. Auf Ubuntu können Sie verwenden sudo apt install python-lxml.

Verwendung

python xpath.py myfile.xml "//mynode"

lxml akzeptiert auch eine URL als Eingabe:

python xpath.py http://www.feedforall.com/sample.xml "//link"

Hinweis : Wenn Ihr XML einen Standard-Namespace ohne Präfix hat (z. B. xmlns=http://abc...), müssen Sie das pPräfix (bereitgestellt durch den 'Hack') in Ihren Ausdrücken verwenden, z. B. //p:moduleum die Module aus einer pom.xmlDatei abzurufen. Falls das pPräfix bereits in Ihrem XML zugeordnet ist, müssen Sie das Skript ändern, um ein anderes Präfix zu verwenden.


Beispiel 2

Ein einmaliges Skript, das dem engen Zweck dient, Modulnamen aus einer Apache-Maven-Datei zu extrahieren. Beachten Sie, wie dem Knotennamen ( module) der Standard-Namespace vorangestellt wird {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)

Dies ist fantastisch, wenn Sie entweder die Installation zusätzlicher Pakete vermeiden möchten oder keinen Zugriff darauf haben. Auf einer Build-Maschine kann ich ein zusätzliches pip installOver apt-getoder einen yumCall rechtfertigen . Vielen Dank!
E. Moffat

0

Die Methode von Yuzem kann verbessert werden, indem die Reihenfolge der <und >-Zeichen in der rdomFunktion und die Variablenzuweisungen umgekehrt werden , so dass:

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

wird:

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

Wenn das Parsen nicht so durchgeführt wird, wird das letzte Tag in der XML-Datei nie erreicht. Dies kann problematisch sein, wenn Sie am Ende der whileSchleife eine weitere XML-Datei ausgeben möchten.


0

Dies funktioniert, wenn Sie XML-Attribute wünschen:

$ 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

Obwohl es so aussieht, als ob "niemals XML, JSON ... von Bash ohne ein geeignetes Tool analysieren" ein guter Rat ist, bin ich anderer Meinung. Wenn dies ein Nebenjob ist, ist es schwierig, nach dem richtigen Werkzeug zu suchen und es dann zu lernen ... Awk kann es in Minuten erledigen. Meine Programme müssen mit allen oben genannten und mehr Arten von Daten arbeiten. Zur Hölle, ich möchte nicht 30 Tools testen, um 5-7-10 verschiedene Formate zu analysieren, die ich brauche, wenn ich das Problem in Minuten lösen kann. XML, JSON oder was auch immer interessieren mich nicht! Ich brauche eine einzige Lösung für alle.

Als Beispiel: Mein SmartHome-Programm führt unsere Häuser. Dabei liest es eine Vielzahl von Daten in zu vielen verschiedenen Formaten, die ich nicht kontrollieren kann. Ich verwende niemals dedizierte, geeignete Tools, da ich nicht mehr als Minuten mit dem Lesen der benötigten Daten verbringen möchte. Mit FS- und RS-Anpassungen funktioniert diese awk-Lösung perfekt für jedes Textformat. Es ist jedoch möglicherweise nicht die richtige Antwort, wenn Ihre Hauptaufgabe darin besteht, hauptsächlich mit vielen Daten in diesem Format zu arbeiten!

Das Problem, XML von Bash zu analysieren, hatte ich gestern. So mache ich das für jedes hierarchische Datenformat. Als Bonus - Ich ordne den Variablen in einem Bash-Skript Daten direkt zu.

Um die Lesbarkeit zu verbessern, werde ich die Lösung schrittweise vorstellen. Aus den OP-Testdaten habe ich eine Datei erstellt: test.xml

Analysieren Sie das XML in Bash und extrahieren Sie die Daten in 90 Zeichen:

awk 'BEGIN { FS="<|>"; RS="\n" }; /host|username|password|dbname/ { print $2, $4 }' test.xml

Normalerweise verwende ich eine besser lesbare Version, da es im wirklichen Leben einfacher ist, Änderungen vorzunehmen, da ich häufig anders testen muss:

awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2,$4}' test.xml

Es ist mir egal, wie das Format heißt. Ich suche nur die einfachste Lösung. In diesem speziellen Fall kann ich anhand der Daten erkennen, dass Newline das Datensatztrennzeichen (RS) und das <> Begrenzungsfeld (FS) ist. In meinem ursprünglichen Fall hatte ich eine komplizierte Indizierung von 6 Werten innerhalb von zwei Datensätzen, um sie zu verknüpfen und festzustellen, wann die Daten vorhanden sind, plus Felder (Datensätze), die möglicherweise vorhanden sind oder nicht. Es dauerte 4 Zeilen awk, um das Problem perfekt zu lösen. Passen Sie die Idee also an jeden Bedarf an, bevor Sie sie verwenden!

Der zweite Teil sieht einfach so aus, als ob eine Zeichenfolge in einer Zeile (RS) gesucht wird, und wenn ja, werden die erforderlichen Felder (FS) ausgedruckt. Ich habe ungefähr 30 Sekunden gebraucht, um den letzten Befehl, den ich auf diese Weise verwendet habe, zu kopieren und anzupassen (viermal länger). Und das ist alles! Fertig in 90 Zeichen.

Aber ich muss die Daten in meinem Skript immer ordentlich in Variablen umwandeln. Ich teste die Konstrukte zuerst so:

awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2"=\""$4"\"" }' test.xml

In einigen Fällen verwende ich printf anstelle von print. Wenn ich sehe, dass alles gut aussieht, beende ich einfach die Zuweisung von Werten zu Variablen. Ich weiß, dass viele denken, "eval" sei "böse", keine Notwendigkeit zu kommentieren :) Trick funktioniert seit Jahren perfekt in allen vier meiner Netzwerke. Aber lernen Sie weiter, wenn Sie nicht verstehen, warum dies eine schlechte Praxis sein kann! Inklusive Bash-Variablenzuweisungen und großem Abstand benötigt meine Lösung 120 Zeichen, um alles zu erledigen.

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"
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.