Comment exécuter des one-liners XPath à partir du shell?

198

Existe-t-il un package, pour Ubuntu et / ou CentOS, qui dispose d'un outil de ligne de commande capable d'exécuter un XPath one-liner comme foo //element@attribute filename.xmlor foo //element@attribute < filename.xmlet de renvoyer les résultats ligne par ligne?

Je suis à la recherche de quelque chose qui me permettrait juste apt-get install fooou yum install fooet puis juste de travailler hors de la boîte, aucun emballage ou autre adaptation nécessaire.

Voici quelques exemples de choses qui se rapprochent:

Nokogiri. Si j'écris ce wrapper, je pourrais appeler le wrapper de la manière décrite ci-dessus:

#!/usr/bin/ruby

require 'nokogiri'

Nokogiri::XML(STDIN).xpath(ARGV[0]).each do |row|
  puts row
end

XML :: XPath. Travaillerait avec ce wrapper:

#!/usr/bin/perl

use strict;
use warnings;
use XML::XPath;

my $root = XML::XPath->new(ioref => 'STDIN');
for my $node ($root->find($ARGV[0])->get_nodelist) {
  print($node->getData, "\n");
}

xpathde XML :: XPath renvoie trop de bruit, -- NODE --et attribute = "value".

xml_grep from XML :: Twig ne peut pas gérer les expressions qui ne renvoient pas d'éléments, et ne peut donc pas être utilisée pour extraire des valeurs d'attribut sans traitement supplémentaire.

ÉDITER:

echo cat //element/@attribute | xmllint --shell filename.xmlrenvoie un bruit similaire à xpath.

xmllint --xpath //element/@attribute filename.xmlrevient attribute = "value".

xmllint --xpath 'string(//element/@attribute)' filename.xml renvoie ce que je veux, mais uniquement pour le premier match.

Pour une autre solution satisfaisant presque la question, voici un XSLT qui peut être utilisé pour évaluer des expressions XPath arbitraires (nécessite dyn: evaluer le support dans le processeur XSLT):

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
    xmlns:dyn="http://exslt.org/dynamic" extension-element-prefixes="dyn">
  <xsl:output omit-xml-declaration="yes" indent="no" method="text"/>
  <xsl:template match="/">
    <xsl:for-each select="dyn:evaluate($pattern)">
      <xsl:value-of select="dyn:evaluate($value)"/>
      <xsl:value-of select="'&#10;'"/>
    </xsl:for-each> 
  </xsl:template>
</xsl:stylesheet>

Courez avec xsltproc --stringparam pattern //element/@attribute --stringparam value . arbitrary-xpath.xslt filename.xml.

clacke
la source
+1 pour une bonne question et pour le brainstorming sur la recherche d'un moyen simple et fiable d'imprimer plusieurs résultats chacun sur une nouvelle ligne
Gilles Quenot
1
Notez que le "bruit" de xpathest sur STDERR et non sur STDOUT.
miken32
@ miken32 Non. Je ne voulais que la valeur de sortie. hastebin.com/ekarexumeg.bash
clacke

Réponses:

278

Vous devriez essayer ces outils:

  • xmlstarlet : peut éditer, sélectionner, transformer ... Non installé par défaut, xpath1
  • xmllint: souvent installé par défaut avec libxml2-utils, xpath1 (vérifiez mon wrapper pour avoir le --xpathcommutateur sur les très anciennes versions et la sortie délimitée par les nouvelles lignes (v <2.9.9)
  • xpath: installé via le module de perl XML::XPath, xpath1
  • xml_grep: installé via le module de perl XML::Twig, xpath1 (utilisation limitée de xpath)
  • xidel: xpath3
  • saxon-lint : mon propre projet, wrapper sur la bibliothèque Java Saxon-HE de @Michael Kay, xpath3

xmllintlivré avec libxml2-utils(peut être utilisé comme shell interactif avec le --shellcommutateur)

xmlstarletest xmlstarlet.

xpath livré avec le module de perl XML::Xpath

xml_grep livré avec le module de perl XML::Twig

xidel est xidel

saxon-linten utilisant SaxonHE 9.6 , XPath 3.x (+ compatibilité rétro)

Ex:

xmllint --xpath '//element/@attribute' file.xml
xmlstarlet sel -t -v "//element/@attribute" file.xml
xpath -q -e '//element/@attribute' file.xml
xidel -se '//element/@attribute' file.xml
saxon-lint --xpath '//element/@attribute' file.xml

.

Gilles Quenot
la source
7
Excellent! xmlstarlet sel -T -t -m '//element/@attribute' -v '.' -n filename.xmlfait exactement ce que je veux!
clacke
2
Remarque: xmlstarlet aurait été abandonné, mais est à nouveau en développement actif.
clacke
6
Remarque: certaines anciennes versions de xmllintne prennent pas en charge l'argument de ligne de commande --xpath, mais la plupart semblent prendre en charge --shell. Sortie légèrement plus sale, mais toujours utile dans une liaison.
kevinarpe
Il me semble toujours avoir du mal à interroger le contenu du nœud, pas un attribut. Quelqu'un peut-il en donner un exemple? Pour une raison quelconque, je trouve toujours xmlstarlet difficile à comprendre et à trouver juste entre la correspondance, la valeur, la racine pour simplement afficher la structure du document, etc. Même avec le premier sel -t -m ... -v ...exemple de cette page: arstechnica.com/information-technology/2005 / 11 / linux-20051115/2 , correspondant à tous les nœuds sauf le dernier et sauvegardant celui-ci pour l'expression de valeur comme mon cas d'utilisation, je n'arrive toujours pas à l'obtenir, je reçois juste une sortie vide ..
Pysis
belle sur la version de xpath - je viens de rencontrer cette limitation de l'excellent xmllint
JonnyRaa
21

Vous pouvez également essayer mon Xidel . Ce n'est pas dans un package dans le référentiel, mais vous pouvez simplement le télécharger à partir de la page Web (il n'a pas de dépendances).

Il a une syntaxe simple pour cette tâche:

xidel filename.xml -e '//element/@attribute' 

Et c'est l'un des rares de ces outils à prendre en charge XPath 2.

BeniBela
la source
2
Xidel a l'air plutôt cool, même si vous devriez probablement mentionner que vous êtes également l'auteur de cet outil que vous recommandez.
FrustratedWithFormsDesigner
1
Saxon et saxon-lint utilisent xpath3;)
Gilles Quenot
Xidel (0..8.win32.zip) apparaît comme ayant un malware sur Virustotal. Alors essayez à vos risques et périls virustotal.com/#/file/…
JGFMK
super - je vais ajouter du xidel à ma boîte à outils de clé personnelle
maoizm
Agréable! J'ai dû lancer une recherche récursive de fichiers XML avec des nœuds correspondant à une requête xpath donnée. Xidel utilisé avec trouver comme suit:find . -name "*.xml" -printf '%p : ' -exec xidel {} -s -e 'expr' \;
Vasan
16

Un package qui est très susceptible d'être installé sur un système l'est déjà python-lxml. Si tel est le cas, cela est possible sans installer de package supplémentaire:

python -c "from lxml.etree import parse; from sys import stdin; print('\n'.join(parse(stdin).xpath('//element/@attribute')))"
clacke
la source
1
Comment passer le nom de fichier?
Ramakrishnan Kannan
5
Cela fonctionne stdin. Cela élimine le besoin d'inclure open()et close()dans une doublure déjà assez longue. Pour analyser un fichier, exécutez simplement python -c "from lxml.etree import parse; from sys import stdin; print '\n'.join(parse(stdin).xpath('//element/@attribute'))" < my_file.xmlet laissez votre shell gérer la recherche, l'ouverture et la fermeture du fichier.
clacke
10

Dans ma recherche pour interroger les fichiers maven pom.xml, j'ai parcouru cette question. Cependant, j'avais les limitations suivantes:

  • doit fonctionner sur plusieurs plates-formes.
  • doit exister sur toutes les distributions Linux majeures sans aucune installation de module supplémentaire
  • doit gérer des fichiers XML complexes tels que les fichiers maven pom.xml
  • syntaxe simple

J'ai essayé plusieurs des solutions ci-dessus sans succès:

  • python lxml.etree ne fait pas partie de la distribution standard de python
  • xml.etree est, mais ne gère pas bien les fichiers maven pom.xml complexes, n'a pas creusé assez profondément
  • python xml.etree ne gère pas les fichiers maven pom.xml pour une raison inconnue
  • xmllint ne fonctionne pas non plus, les core dumps souvent sur ubuntu 12.04 "xmllint: using libxml version 20708"

La solution que j'ai rencontrée qui est stable, courte et qui fonctionne sur de nombreuses plates-formes et qui est mature est la librairie rexml intégrée à ruby:

ruby -r rexml/document -e 'include REXML; 
     puts XPath.first(Document.new($stdin), "/project/version/text()")' < pom.xml

Ce qui m'a inspiré pour trouver celui-ci, ce sont les articles suivants:

Mike
la source
1
Ce sont des critères encore plus étroits que la question, donc cela correspond certainement à une réponse. Je suis sûr que de nombreuses personnes qui se sont heurtées à votre situation seront aidées par vos recherches. Je garde xmlstarletcomme réponse acceptée, car elle correspond à mes critères plus larges et elle est vraiment soignée . Mais j'aurai probablement besoin de votre solution de temps en temps.
clacke
2
J'ajouterais que pour éviter les citations autour du résultat , utilisez putsplutôt que pdans la commande Ruby.
tooomg
10

Saxon le fera non seulement pour XPath 2.0, mais aussi pour XQuery 1.0 et (dans la version commerciale) 3.0. Il ne s'agit pas d'un package Linux, mais d'un fichier jar. La syntaxe (que vous pouvez facilement envelopper dans un simple script) est

java net.sf.saxon.Query -s:source.xml -qs://element/attribute

MISE À JOUR 2020

Saxon 10.0 inclut l'outil Gizmo, qui peut être utilisé de manière interactive ou par lots à partir de la ligne de commande. Par exemple

java net.sf.saxon.Gizmo -s:source.xml
/>show //element/@attribute
/>quit
Michael Kay
la source
SaxonB est dans Ubuntu, package libsaxonb-java, mais si je lance , saxonb-xquery -qs://element/@attribute -s:filename.xmlj'obtiens le SENR0001: Cannot serialize a free-standing attribute nodemême problème qu'avec par exemple xml_grep.
clacke
3
Si vous souhaitez voir tous les détails du nœud d'attribut sélectionné par cette requête, utilisez l'option -wrap sur la ligne de commande. Si vous voulez simplement la valeur de chaîne de l'attribut, ajoutez / string () à la requête.
Michael Kay
Merci. L'ajout de / string () se rapproche. Mais il génère un en-tête XML et met tous les résultats sur une seule ligne, donc toujours pas de cigare.
clacke
2
Si vous ne voulez pas d'en-tête XML, ajoutez l'option! Method = text.
Michael Kay
Pour utiliser l'espace de noms, ajoutez-le -qscomme ceci:'-qs:declare namespace mets="http://www.loc.gov/METS/";/mets:mets/mets:dmdSec'
igo
5

Vous pourriez également être intéressé par xsh . Il dispose d'un mode interactif où vous pouvez faire ce que vous voulez avec le document:

open 1.xml ;
ls //element/@id ;
for //p[@class="first"] echo text() ;
choroba
la source
Il ne semble pas être disponible sous forme de package, du moins pas dans Ubuntu.
clacke
1
@clacke: Ce n'est pas le cas, mais il peut être installé à partir du CPAN par cpan XML::XSH2.
choroba
@choroba, j'ai essayé cela sur OS X, mais l'installation a échoué, avec une sorte d'erreur de makefile.
cnst
@cnst: Avez-vous installé XML :: LibXML?
choroba
@choroba, je ne sais pas; mais mon point est que, cpan XML::XSH2ne parvient pas à installer quoi que ce soit.
cnst
5

La réponse de clacke est excellente mais je pense que cela ne fonctionne que si votre source est du XML bien formé, pas du HTML normal.

Donc, pour faire de même pour le contenu Web normal - des documents HTML qui ne sont pas nécessairement du XML bien formé:

echo "<p>foo<div>bar</div><p>baz" | python -c "from sys import stdin; \
from lxml import html; \
print '\n'.join(html.tostring(node) for node in html.parse(stdin).xpath('//p'))"

Et d'utiliser à la place html5lib (pour vous assurer d'obtenir le même comportement d'analyse que les navigateurs Web, car comme les analyseurs de navigateur, html5lib est conforme aux exigences d'analyse de la spécification HTML).

echo "<p>foo<div>bar</div><p>baz" | python -c "from sys import stdin; \
import html5lib; from lxml import html; \
doc = html5lib.parse(stdin, treebuilder='lxml', namespaceHTMLElements=False); \
print '\n'.join(html.tostring(node) for node in doc.xpath('//p'))
soiree
la source
Oui, je suis tombé dans ma propre hypothèse dans la question, que XPath implique XML. Cette réponse est un bon complément aux autres ici, et merci de m'avoir fait part de html5lib!
clacke
3

Semblable aux réponses de Mike et de Clacke, voici le one-liner python (en utilisant python> = 2.5) pour obtenir la version de construction à partir d'un fichier pom.xml qui contourne le fait que les fichiers pom.xml n'ont normalement pas de dtd ou espace de noms par défaut, donc ne semble pas bien formé à libxml:

python -c "import xml.etree.ElementTree as ET; \
  print(ET.parse(open('pom.xml')).getroot().find('\
  {http://maven.apache.org/POM/4.0.0}version').text)"

Testé sur Mac et Linux, et ne nécessite l'installation d'aucun package supplémentaire.

pdr
la source
2
Je l'ai utilisé aujourd'hui! Nos serveurs de build n'avaient ni lxmlni xmllint, ni même Ruby. Dans l'esprit du format de ma propre réponse , je l'ai écrit comme python3 -c "from xml.etree.ElementTree import parse; from sys import stdin; print(parse(stdin).find('.//element[subelement=\"value\"]/othersubelement').text)" <<< "$variable_containing_xml"dans bash. .getroot()ne semble pas nécessaire.
clacke
2

En plus de XML :: XSH et XML :: XSH2, il existe des greputilitaires semblables à sucer comme App::xml_grep2et XML::Twig(qui incluent xml_grepplutôt que xml_grep2). Ceux-ci peuvent être très utiles lorsque vous travaillez sur des fichiers XML volumineux ou nombreux pour des oneliners ou des Makefilecibles rapides . XML::Twigest particulièrement agréable à perlutiliser pour une approche de script lorsque vous voulez un peu plus de traitement que votre $SHELLetxmllint xstlproc offre .

Le schéma de numérotation dans les noms d'applications indique que les versions «2» sont des versions plus récentes / plus récentes du même outil qui peut nécessiter des versions ultérieures d'autres modules (ou de perllui - même).

G. Cito
la source
xml_grep2 -t //element@attribute filename.xmlfonctionne et fait ce que j'attends ( xml_grep --root //element@attribute --text_only filename.xmlne le fait toujours pas, renvoie une erreur "expression non reconnue"). Génial!
clacke
Et quoi xml_grep --pretty_print --root '//element[@attribute]' --text_only filename.xml? Je ne sais pas ce qui se passe là-bas ou ce que XPath dit []dans ce cas, mais entourer un @attributeavec des crochets fonctionne pour xml_grepet xml_grep2.
G. Cito
Je veux dire //element/@attributenon //element@attribute. Je ne peux pas le modifier apparemment, mais le laisser là plutôt que supprimer + remplacer pour ne pas confondre l'historique de cette discussion.
clacke
//element[@attribute]sélectionne les éléments de type elementqui ont un attribut attribute. Je ne veux pas l'élément, seulement l'attribut. <element attribute='foo'/>devrait me donner foo, pas le plein <element attribute='foo'/>.
clacke
... et --text_onlydans ce contexte me donne la chaîne vide dans le cas d'un élément comme <element attribute='foo'/>sans nœud de texte à l'intérieur.
clacke
2

Il convient de mentionner que nokogiri lui-même est livré avec un outil de ligne de commande, qui doit être installé avec gem install nokogiri .

Vous trouverez peut-être ce billet de blog utile .

Geoff Nixon
la source
2

J'ai essayé quelques utilitaires XPath en ligne de commande et quand j'ai réalisé que je passais trop de temps à chercher sur Google et à comprendre comment ils fonctionnent, j'ai donc écrit l'analyseur XPath le plus simple possible en Python, qui a fait ce dont j'avais besoin.

Le script ci-dessous montre la valeur de la chaîne si l'expression XPath est évaluée comme une chaîne, ou affiche le sous-nœud XML entier si le résultat est un nœud:

#!/usr/bin/env python
import sys
from lxml import etree

tree = etree.parse(sys.argv[1])
xpath = sys.argv[2]

for e in tree.xpath(xpath):

    if isinstance(e, str):
        print(e)
    else:
        print((e.text and e.text.strip()) or etree.tostring(e))

Il utilise lxml- un analyseur XML rapide écrit en C qui n'est pas inclus dans la bibliothèque python standard. Installez-le avec pip install lxml. Sous Linux / OSX, il peut être nécessaire de préfixersudo .

Usage:

python xmlcat.py file.xml "//mynode"

lxml peut également accepter une URL en entrée:

python xmlcat.py http://example.com/file.xml "//mynode" 

Extrayez l'attribut url sous un nœud de boîtier, c'est <enclosure url="http:...""..>)-à- dire :

python xmlcat.py xmlcat.py file.xml "//enclosure/@url"

Xpath dans Google Chrome

En guise de remarque secondaire: si par hasard vous souhaitez exécuter une expression XPath sur le balisage d'une page Web, vous pouvez le faire directement à partir des outils de développement Chrome: cliquez avec le bouton droit sur la page dans Chrome> sélectionnez Inspecter, puis dans les DevTools console collez votre expression XPath comme $x("//spam/eggs").

Obtenez tous les auteurs sur cette page:

$x("//*[@class='user-details']/a/text()")
ccpizza
la source
Pas une seule ligne, et lxmla déjà été mentionné dans deux autres réponses des années avant la vôtre.
clacke
2

Voici un cas d'utilisation de xmlstarlet pour extraire les données des éléments imbriqués elem1, elem2 vers une ligne de texte de ce type de XML (montrant également comment gérer les espaces de noms):

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<mydoctype xmlns="http://xml-namespace-uri" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xml-namespace-uri http://xsd-uri" format="20171221A" date="2018-05-15">

  <elem1 time="0.586" length="10.586">
      <elem2 value="cue-in" type="outro" />
  </elem1>

</mydoctype>

La sortie sera

0.586 10.586 cue-in outro

Dans cet extrait, -m correspond à l'élem2 imbriqué, -v renvoie les valeurs d'attribut (avec expressions et adressage relatif), -o texte littéral, -n ajoute une nouvelle ligne:

xml sel -N ns="http://xml-namespace-uri" -t -m '//ns:elem1/ns:elem2' \
 -v ../@time -o " " -v '../@time + ../@length' -o " " -v @value -o " " -v @type -n file.xml

Si plus d'attributs sont nécessaires à partir de elem1, on peut le faire comme ceci (montrant également la fonction concat ()):

xml sel -N ns="http://xml-namespace-uri" -t -m '//ns:elem1/ns:elem2/..' \
 -v 'concat(@time, " ", @time + @length, " ", ns:elem2/@value, " ", ns:elem2/@type)' -n file.xml

Notez la complication (IMO inutile) avec les espaces de noms (ns, déclaré avec -N), qui m'a presque fait abandonner xpath et xmlstarlet, et écrire un rapide convertisseur ad-hoc.

diemo
la source
xmlstarlet est génial, mais la réponse de classement acceptée et principale le mentionne déjà. Les informations sur la façon de gérer les espaces de noms peuvent avoir été pertinentes en tant que commentaire, voire pas du tout. Toute personne rencontrant des problèmes avec les espaces de noms et xmlstarlet peut trouver une excellente discussion dans la documentation
clacke
2
Bien sûr, @clacke, xmlstarlet a été mentionné à plusieurs reprises, mais aussi qu'il est difficile à saisir, et sous-documenté. Pendant une heure, je devinais comment extraire des informations d'éléments imbriqués. J'aurais aimé avoir cet exemple, c'est pourquoi je le poste ici pour éviter aux autres cette perte de temps (et l'exemple est trop long pour un commentaire).
diemo
2

Mon script Python xgrep.py fait exactement cela. Afin de rechercher tous les attributs attributedes éléments elementdans les fichiers filename.xml ..., vous l'exécuterez comme suit:

xgrep.py "//element/@attribute" filename.xml ...

Il existe différents commutateurs pour contrôler la sortie, par exemple -cpour compter les correspondances, -ipour indenter les parties correspondantes et -lpour sortir uniquement les noms de fichiers.

Le script n'est pas disponible en tant que package Debian ou Ubuntu, mais toutes ses dépendances le sont.

Andreas Nolda
la source
Et vous hébergez sur sourcehut! Agréable!
clacke
1

Étant donné que ce projet est apparemment assez nouveau, consultez https://github.com/jeffbr13/xq , semble être un wrapper lxml, mais c'est tout ce dont vous avez vraiment besoin (et publié des solutions ad hoc utilisant lxml dans d'autres réponses également)

mgrandi
la source
1

Je n'étais pas satisfait des one-liners Python pour les requêtes HTML XPath, alors j'ai écrit le mien. Suppose que vous avez installé le python-lxmlpackage ou exécuté pip install --user lxml:

function htmlxpath() { python -c 'for x in __import__("lxml.html").html.fromstring(__import__("sys").stdin.read()).xpath(__import__("sys").argv[1]): print(x)' $1 }

Une fois que vous l'avez, vous pouvez l'utiliser comme dans cet exemple:

> curl -s https://slashdot.org | htmlxpath '//title/text()'
Slashdot: News for nerds, stuff that matters
d33tah
la source
0

Installez la base de données BaseX , puis utilisez son "mode de ligne de commande autonome" comme ceci:

basex -i - //element@attribute < filename.xml

ou

basex -i filename.xml //element@attribute

Le langage de requête est en fait XQuery (3.0), pas XPath, mais comme XQuery est un sur-ensemble de XPath, vous pouvez utiliser les requêtes XPath sans jamais vous en rendre compte.

igneus
la source