173 votes

Comment analyser le XML en Bash ?

Idéalement, ce que je voudrais être en mesure de faire est :

cat xhtmlfile.xhtml |
getElementViaXPath --path='/html/head/title' |
sed -e 's%(^<title>|</title>$)%%g' > titleOfXHTMLPage.txt

183voto

chad Points 1084

C'est vraiment juste une explication de Yuzem's répondre, mais je n'avais pas l'impression qu'autant d'édition devait être faite par quelqu'un d'autre, et les commentaires ne permettent pas le formatage, donc...

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

Appelons cela "read_dom" au lieu de "rdom", espaçons-le un peu et utilisons des variables plus longues :

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

Ok, donc il définit une fonction appelée read_dom. La première ligne rend IFS (le séparateur de champ d'entrée) local à cette fonction et le change en >. Cela signifie que lorsque vous lisez des données, au lieu d'être automatiquement séparées par un espace, une tabulation ou une nouvelle ligne, elles sont séparées par '>'. La ligne suivante dit de lire l'entrée de stdin, et au lieu de s'arrêter à une nouvelle ligne, de s'arrêter quand vous voyez un caractère '<' (le drapeau -d pour deliminator). Ce qui est lu est ensuite divisé en utilisant l'IFS et assigné aux variables ENTITY et CONTENT. Prenons l'exemple suivant :

<tag>value</tag>

Le premier appel à read_dom obtient une chaîne vide (puisque le '<' est le premier caractère). Elle est divisée par IFS en seulement '', puisqu'il n'y a pas de caractère '>'. Read attribue alors une chaîne vide aux deux variables. Le deuxième appel obtient la chaîne 'tag>value'. Elle est alors divisée par l'IFS en deux champs 'tag' et 'value'. Read assigne ensuite les variables comme suit : ENTITY=tag et CONTENT=value . Le troisième appel obtient la chaîne '/tag>'. Celle-ci est divisée par l'IFS en deux champs '/tag' et ''. Read assigne ensuite les variables comme : ENTITY=/tag et CONTENT= . Le quatrième appel renverra un état non nul car nous avons atteint la fin du fichier.

Maintenant sa boucle while a été nettoyée un peu pour correspondre à ce qui précède :

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

La première ligne dit simplement "tant que la fonction read_dom renvoie un état nul, faites ce qui suit". La deuxième ligne vérifie si l'entité que nous venons de voir est "title". La ligne suivante fait écho au contenu de la balise. La quatrième ligne sort. Si ce n'était pas l'entité "title", la boucle se répète à la sixième ligne. Nous redirigeons "xhtmlfile.xhtml" vers l'entrée standard (pour la balise read_dom ) et redirige la sortie standard vers "titleOfXHTMLPage.txt" (l'écho obtenu plus tôt dans la boucle).

Maintenant, étant donné ce qui suit (similaire à ce que vous obtenez en listant un seau sur S3) pour 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>

et la boucle suivante :

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

Tu devrais avoir :

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

Donc si nous avons écrit un while boucle comme celle de Yuzem :

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

Nous obtiendrons une liste de tous les fichiers dans le seau S3.

EDIT Si pour une raison quelconque local IFS=\> ne fonctionne pas pour vous et que vous l'avez défini globalement, vous devez le réinitialiser à la fin de la fonction comme :

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

Sinon, tout découpage de ligne que vous ferez plus tard dans le script sera perturbé.

EDIT 2 Pour séparer les paires nom/valeur d'attributs, vous pouvez augmenter la fonction read_dom() comme ça :

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

Puis écrivez votre fonction pour analyser et obtenir les données que vous voulez comme ceci :

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
}

Alors pendant que vous read_dom appelez parse_dom :

while read_dom; do
    parse_dom
done

Puis, étant donné l'exemple de balisage suivant :

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

Vous devriez obtenir ce résultat :

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

EDIT 3 un autre utilisateur a dit qu'ils avaient des problèmes avec FreeBSD et a suggéré de sauvegarder le statut de sortie de read et de le retourner à la fin de read_dom comme :

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

Je ne vois pas pourquoi ça ne marcherait pas.

77voto

Nat Points 6434

Les outils de ligne de commande qui peuvent être appelés à partir des scripts du shell comprennent :

  • 4xpath - autour de la ligne de commande de Python. 4Suite paquet

  • XMLStarlet

  • xpath - enveloppe de ligne de commande autour de la bibliothèque XPath de Perl

    sudo apt-get install libxml-xpath-perl
  • Xidel - Fonctionne aussi bien avec les URL qu'avec les fichiers. Fonctionne également avec JSON

J'utilise aussi xmllint et xsltproc avec des petits scripts de transformation XSL pour faire du traitement XML à partir de la ligne de commande ou dans des scripts de shell.

71voto

Yuzem Points 385

Vous pouvez le faire très facilement en utilisant uniquement bash. Il vous suffit d'ajouter cette fonction :

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

Maintenant vous pouvez utiliser rdom comme read mais pour les documents html. Lorsqu'il est appelé, rdom attribue l'élément à la variable E et le contenu à la var C.

Par exemple, pour faire ce que vous vouliez faire :

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

27voto

Grisha Points 69

Vous pouvez utiliser l'utilitaire xpath. Il est installé avec le paquetage Perl XML-XPath.

Utilisation :

/usr/bin/xpath [filename] query

o XMLStarlet . Pour l'installer sur opensuse, utilisez :

sudo zypper install xmlstarlet

ou essayez cnf xml sur d'autres plateformes.

18voto

teknopaul Points 1102

C'est suffisant...

xpath xhtmlfile.xhtml '/html/head/title/text()' > titleOfXHTMLPage.txt

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X