Wie führe ich XPath-Einzeiler aus der Shell aus?


192

Gibt es ein Paket für Ubuntu und / oder CentOS, das über ein Befehlszeilentool verfügt, mit dem ein XPath-Einzeiler wie foo //element@attribute filename.xmloder ausgeführt foo //element@attribute < filename.xmlund die Ergebnisse zeilenweise zurückgegeben werden können?

Ich bin auf der Suche nach etwas, das es mir ermöglicht, einfach apt-get install foooder yum install foosofort zu arbeiten, ohne dass Wrapper oder andere Anpassungen erforderlich sind.

Hier sind einige Beispiele für Dinge, die nahe kommen:

Nokogiri. Wenn ich diesen Wrapper schreibe, könnte ich den Wrapper wie oben beschrieben aufrufen:

#!/usr/bin/ruby

require 'nokogiri'

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

XML :: XPath. Würde mit diesem Wrapper funktionieren:

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

xpathvon XML :: XPath gibt zu viel Rauschen zurück, -- NODE --und attribute = "value".

xml_grep from XML :: Twig kann keine Ausdrücke verarbeiten, die keine Elemente zurückgeben, und kann daher nicht zum Extrahieren von Attributwerten ohne weitere Verarbeitung verwendet werden.

BEARBEITEN:

echo cat //element/@attribute | xmllint --shell filename.xmlgibt Rauschen ähnlich wie zurück xpath.

xmllint --xpath //element/@attribute filename.xmlkehrt zurück attribute = "value".

xmllint --xpath 'string(//element/@attribute)' filename.xml gibt zurück, was ich will, aber nur für das erste Match.

Für eine andere Lösung, die die Frage fast befriedigt, ist hier ein XSLT, das zum Auswerten beliebiger XPath-Ausdrücke verwendet werden kann (erfordert dyn: evaluiere die Unterstützung im XSLT-Prozessor):

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

Laufen Sie mit xsltproc --stringparam pattern //element/@attribute --stringparam value . arbitrary-xpath.xslt filename.xml.


+1 für gute Fragen und für das Brainstorming über die Suche nach einem einfachen und zuverlässigen Weg, um mehrere Ergebnisse jeweils auf eine neue
Zeile

1
Beachten Sie, dass das "Rauschen" von xpathauf STDERR und nicht auf STDOUT ist.
miken32

@ miken32 Nein. Ich wollte nur den Wert für die Ausgabe. hastebin.com/ekarexumeg.bash
clacke

Antworten:


271

Sie sollten diese Tools ausprobieren:

  • xmlstarlet : kann bearbeiten, auswählen, transformieren ... Nicht standardmäßig installiert, xpath1
  • xmllint: Häufig standardmäßig installiert mit libxml2-utils, xpath1 (check out my Wrapper haben --xpathSchalter begrenzt die Ausgabe auf sehr alten Versionen und neue Zeilen (v <2.9.9)
  • xpath: installiert über das Perl-Modul XML::XPathxpath1
  • xml_grep: installiert über das Perl-Modul XML::Twigxpath1 (eingeschränkte xpath-Nutzung)
  • xidel: xpath3
  • saxon-lint : Mein eigenes Projekt, Wrapper über @Michael Kays Saxon-HE Java-Bibliothek, xpath3

xmllint kommt mit libxml2-utils (kann als interaktive Shell mit dem --shellSchalter verwendet werden)

xmlstarlet ist xmlstarlet .

xpath kommt mit Perl-Modul XML::Xpath

xml_grep kommt mit Perl-Modul XML::Twig

xidel ist xidel

saxon-lintmit SaxonHE 9.6 , XPath 3.x (+ Retro-Kompatibilität)

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

.


7
Ausgezeichnet! xmlstarlet sel -T -t -m '//element/@attribute' -v '.' -n filename.xmlmacht genau das was ich will!
Clacke

2
Hinweis: Es wurde gemunkelt, dass xmlstarlet aufgegeben wurde, aber jetzt wieder aktiv entwickelt wird.
Clacke

6
Hinweis: Einige ältere Versionen von xmllintunterstützen kein Befehlszeilenargument --xpath, die meisten scheinen dies jedoch zu unterstützen --shell. Etwas schmutzigere Ausgabe, aber dennoch nützlich beim Binden.
Kevinarpe

Ich habe immer noch Probleme beim Abfragen von Knoteninhalten, nicht von Attributen. Kann jemand ein Beispiel dafür geben? Aus irgendeinem Grund fällt es mir immer noch schwer, xmlstarlet herauszufinden und zwischen Matching, Wert, Root, um nur die Dokumentstruktur anzuzeigen usw. zu finden. Selbst mit dem ersten sel -t -m ... -v ...Beispiel auf dieser Seite: arstechnica.com/information-technology/2005 / 11 / linux-20051115/2 , der alle bis auf den letzten Knoten abgleichen und diesen für den Wertausdruck wie meinen Anwendungsfall speichern kann, kann ich immer noch nicht
verstehen

Nettes auf der Version von xpath - Ich würde gerade auf diese Einschränkung des ansonsten ausgezeichneten
xmllint stoßen

20

Sie können auch meinen Xidel ausprobieren . Es befindet sich nicht in einem Paket im Repository, aber Sie können es einfach von der Webseite herunterladen (es hat keine Abhängigkeiten).

Es hat eine einfache Syntax für diese Aufgabe:

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

Und es ist eines der seltenen dieser Tools, das XPath 2 unterstützt.


2
Xidel sieht ziemlich cool aus, obwohl Sie wahrscheinlich erwähnen sollten, dass Sie auch der Autor dieses von Ihnen empfohlenen Tools sind.
FrustratedWithFormsDesigner

1
Saxon und Saxon-Lint verwenden xpath3;)
Gilles Quenot

Xidel (0..8.win32.zip) weist Malware auf Virustotal auf. Versuchen Sie es also auf eigenes Risiko virustotal.com/#/file/…
JGFMK

großartig - ich werde xidel zu meinem persönlichen Schraubenschlüssel-Werkzeugkasten hinzufügen
maoizm

15

Ein Paket, das sehr wahrscheinlich bereits auf einem System installiert ist, ist python-lxml. Wenn ja, ist dies möglich, ohne ein zusätzliches Paket zu installieren:

python -c "from lxml.etree import parse; from sys import stdin; print '\n'.join(parse(stdin).xpath('//element/@attribute'))"

1
Wie übergebe ich den Dateinamen?
Ramakrishnan Kannan

4
Das funktioniert weiter stdin. Dadurch entfällt die Notwendigkeit , einschließlich open()und close()in ein bereits recht lang Einzeiler. Um eine Datei zu analysieren, führen Sie sie einfach aus python -c "from lxml.etree import parse; from sys import stdin; print '\n'.join(parse(stdin).xpath('//element/@attribute'))" < my_file.xmlund lassen Sie Ihre Shell die Datei suchen, öffnen und schließen.
Clacke

10

Bei meiner Suche nach maven pom.xml-Dateien bin ich auf diese Frage gestoßen. Ich hatte jedoch die folgenden Einschränkungen:

  • muss plattformübergreifend ausgeführt werden.
  • muss auf allen wichtigen Linux-Distributionen ohne zusätzliche Modulinstallation vorhanden sein
  • muss komplexe XML-Dateien wie maven pom.xml-Dateien verarbeiten
  • einfache Syntax

Ich habe viele der oben genannten erfolglos ausprobiert:

  • python lxml.etree ist nicht Teil der Standard-Python-Distribution
  • xml.etree ist aber nicht in der Lage, komplexe maven pom.xml-Dateien gut zu behandeln, haben nicht tief genug gegraben
  • python xml.etree verarbeitet aus unbekannten Gründen keine maven pom.xml-Dateien
  • xmllint funktioniert auch nicht, Core-Dumps häufig unter Ubuntu 12.04 "xmllint: using libxml version 20708"

Die Lösung, auf die ich gestoßen bin, die stabil, kurz und auf vielen Plattformen funktioniert und die ausgereift ist, ist die in Ruby integrierte rexml lib:

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

Was mich dazu inspirierte, diesen zu finden, waren die folgenden Artikel:


1
Das sind noch engere Kriterien als die Frage, daher passt sie definitiv als Antwort. Ich bin sicher, dass viele Menschen, die auf Ihre Situation gestoßen sind, durch Ihre Forschung unterstützt werden. Ich behalte xmlstarletdie akzeptierte Antwort bei, weil sie meinen umfassenderen Kriterien entspricht und wirklich ordentlich ist . Aber ich werde wahrscheinlich von Zeit zu Zeit Verwendung für Ihre Lösung haben.
Clacke

2
Ich würde das hinzufügen, um Anführungszeichen um das Ergebnis zu vermeiden , putsanstatt pim Ruby-Befehl zu verwenden.
TomG

10

Saxon wird dies nicht nur für XPath 2.0 tun, sondern auch für XQuery 1.0 und (in der kommerziellen Version) 3.0. Es kommt nicht als Linux-Paket, sondern als JAR-Datei. Syntax (die Sie leicht in ein einfaches Skript einbinden können) ist

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

2020 UPDATE

Saxon 10.0 enthält das Gizmo-Tool, das interaktiv oder stapelweise über die Befehlszeile verwendet werden kann. Beispielsweise

java net.sf.saxon.Gizmo -s:source.xml
/>show //element/@attribute
/>quit

SaxonB ist in Ubuntu, Paket libsaxonb-java, aber wenn ich laufe, saxonb-xquery -qs://element/@attribute -s:filename.xmlbekomme ich das SENR0001: Cannot serialize a free-standing attribute nodegleiche Problem wie zB xml_grep.
Clacke

3
Wenn Sie alle Details des von dieser Abfrage ausgewählten Attributknotens anzeigen möchten, verwenden Sie die Option -wrap in der Befehlszeile. Wenn Sie nur den Zeichenfolgenwert des Attributs möchten, fügen Sie der Abfrage / string () hinzu.
Michael Kay

Vielen Dank. Das Hinzufügen von / string () kommt näher. Aber es gibt einen XML-Header aus und fasst alle Ergebnisse in einer Zeile zusammen, also immer noch keine Zigarre.
Clacke

2
Wenn Sie keinen XML-Header möchten, fügen Sie die Option! Method = text hinzu.
Michael Kay

Um den Namespace zu verwenden, fügen Sie ihn folgendermaßen hinzu -qs:'-qs:declare namespace mets="http://www.loc.gov/METS/";/mets:mets/mets:dmdSec'
igo

5

Sie könnten auch an xsh interessiert sein . Es verfügt über einen interaktiven Modus, in dem Sie mit dem Dokument beliebig viel tun können:

open 1.xml ;
ls //element/@id ;
for //p[@class="first"] echo text() ;

Es scheint nicht als Paket verfügbar zu sein, zumindest nicht in Ubuntu.
Clacke

1
@clacke: Ist es nicht, aber es kann von CPAN aus installiert werden cpan XML::XSH2.
Choroba

@choroba, ich habe das unter OS X versucht, aber es konnte nicht installiert werden, mit einer Art Makefile-Fehler.
cnst

@cnst: Haben Sie XML :: LibXML installiert?
Choroba

@ Choroba, ich weiß es nicht; aber mein Punkt ist, dass cpan XML::XSH2nichts installiert werden kann.
cnst

5

Die Antwort von clacke ist großartig, aber ich denke, sie funktioniert nur, wenn Ihre Quelle wohlgeformtes XML ist, nicht normales HTML.

Um dasselbe für normale Webinhalte zu tun - HTML-Dokumente, die nicht unbedingt gut geformtes XML sind:

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

Verwenden Sie stattdessen html5lib (um sicherzustellen, dass Sie dasselbe Parsing-Verhalten wie Webbrowser erhalten, da html5lib wie Browser-Parser den Parsing-Anforderungen in der HTML-Spezifikation entspricht).

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

Ja, ich bin auf meine eigene Annahme in der Frage hereingefallen, dass XPath XML impliziert. Diese Antwort ist eine gute Ergänzung zu den anderen hier und danke, dass Sie mich über html5lib informiert haben!
Clacke

3

Ähnlich wie bei Mike und Clacke gibt es hier den Python-Einzeiler (mit Python> = 2.5), um die Build-Version aus einer pom.xml-Datei abzurufen, die die Tatsache umgeht, dass pom.xml-Dateien normalerweise kein dtd oder haben Standard-Namespace, daher erscheint libxml nicht wohlgeformt:

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

Getestet auf Mac und Linux und erfordert keine Installation zusätzlicher Pakete.


2
Ich habe das heute benutzt! Unsere Build-Server hatten weder lxmlnoch xmllintnoch Ruby. Im Geiste des Formats in meiner eigenen Antwort schrieb ich es wie python3 -c "from xml.etree.ElementTree import parse; from sys import stdin; print(parse(stdin).find('.//element[subelement=\"value\"]/othersubelement').text)" <<< "$variable_containing_xml"in Bash. .getroot()scheint nicht notwendig.
Clacke

2

Zusätzlich zu XML :: XSH und XML :: XSH2 gibt es einige grepähnliche Dienstprogramme, die als App::xml_grep2und saugen XML::Twig(was xml_grepeher beinhaltet als xml_grep2). Diese können sehr nützlich sein, wenn Sie an einer großen oder zahlreichen XML-Dateien für schnelle Oneliner oder MakefileZiele arbeiten. XML::TwigEs ist besonders schön, mit ihm für einen perlScripting-Ansatz zu arbeiten, wenn Sie etwas mehr Verarbeitung als Ihre $SHELLund Ihr xmllint xstlprocAngebot wünschen .

Das Nummerierungsschema in den Anwendungsnamen gibt an, dass die "2" -Versionen neuere / spätere Versionen im Wesentlichen desselben Tools sind, für die möglicherweise spätere Versionen anderer Module (oder von sich perlselbst) erforderlich sind .


xml_grep2 -t //element@attribute filename.xmlfunktioniert und macht das, was ich erwarte ( xml_grep --root //element@attribute --text_only filename.xmlimmer noch nicht, gibt einen Fehler "nicht erkannter Ausdruck" zurück). Toll!
Clacke

Was ist mit xml_grep --pretty_print --root '//element[@attribute]' --text_only filename.xml? Ich bin mir nicht sicher, was dort vor sich geht oder was XPath []in diesem Fall sagt , aber das Umgeben eines @attributemit eckigen Klammern funktioniert für xml_grepund xml_grep2.
G. Cito

Ich meine //element/@attributenicht //element@attribute. Ich kann es anscheinend nicht bearbeiten, aber lasse es dort, anstatt es zu löschen + zu ersetzen, um den Verlauf dieser Diskussion nicht zu verwirren.
Clacke

//element[@attribute]Wählt Elemente vom Typ aus element, die ein Attribut haben attribute. Ich möchte nicht das Element, nur das Attribut. <element attribute='foo'/>sollte mir geben foo, nicht das volle <element attribute='foo'/>.
Clacke

... und --text_onlyin diesem Zusammenhang gibt mir die leere Zeichenfolge bei einem Element wie <element attribute='foo'/>ohne Textknoten.
Clacke


2

Ich habe ein paar Befehlszeilen-XPath-Dienstprogramme ausprobiert und als mir klar wurde, dass ich zu viel Zeit damit verbringe, zu googeln und herauszufinden, wie sie funktionieren, habe ich den einfachsten XPath-Parser in Python geschrieben, der genau das tat, was ich brauchte.

Das folgende Skript zeigt den Zeichenfolgenwert an, wenn der XPath-Ausdruck eine Zeichenfolge ergibt, oder den gesamten XML-Unterknoten, wenn das Ergebnis ein Knoten ist:

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

Es verwendet lxml- einen schnellen XML-Parser, der in C geschrieben ist und nicht in der Standard-Python-Bibliothek enthalten ist. Installieren Sie es mit pip install lxml. Unter Linux / OSX muss möglicherweise ein Präfix mit verwendet werden sudo.

Verwendung:

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

lxml kann auch eine URL als Eingabe akzeptieren:

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

Extrahieren Sie das URL-Attribut unter einem Gehäuseknoten, dh <enclosure url="http:...""..>):

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

Xpath in Google Chrome

Als nicht verwandte Randnotiz: Wenn Sie zufällig einen XPath-Ausdruck für das Markup einer Webseite ausführen möchten, können Sie dies direkt in den Chrome-Devtools tun: Klicken Sie mit der rechten Maustaste auf die Seite in Chrome, wählen Sie Inspizieren und dann in den DevTools Konsole fügen Sie Ihren XPath-Ausdruck ein als $x("//spam/eggs").

Holen Sie sich alle Autoren auf dieser Seite:

$x("//*[@class='user-details']/a/text()")

Kein Einzeiler und lxmlwurde bereits in zwei anderen Antworten Jahre vor Ihrer erwähnt.
Clacke

2

Hier ist ein Anwendungsfall für xmlstarlet, um Daten aus verschachtelten Elementen elem1, elem2 in eine Textzeile aus diesem XML-Typ zu extrahieren (und zeigt auch, wie mit Namespaces umgegangen wird):

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

Die Ausgabe wird sein

0.586 10.586 cue-in outro

In diesem Snippet stimmt -m mit dem verschachtelten elem2 überein, -v gibt Attributwerte aus (mit Ausdrücken und relativer Adressierung), -o Literaltext, -n fügt eine neue Zeile hinzu:

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

Wenn mehr Attribute von elem1 benötigt werden, können Sie dies folgendermaßen tun (wobei auch die Funktion concat () angezeigt wird):

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

Beachten Sie die (IMO unnötig) Komplikation mit Namespaces (ns, deklariert mit -N), bei der ich xpath und xmlstarlet fast aufgegeben und einen schnellen Ad-hoc-Konverter geschrieben habe.


xmlstarlet ist großartig, aber die akzeptierte und wichtigste Antwort erwähnt es bereits. Die Informationen zum Umgang mit Namespaces könnten, wenn überhaupt, als Kommentar relevant gewesen sein. Jeder, der auf Probleme mit Namespaces und xmlstarlet stößt, kann eine hervorragende Diskussion in der Dokumentation finden
clacke

2
Sicher, @clacke, xmlstarlet wurde schon mehrmals erwähnt, aber auch, dass es schwer zu verstehen und unterdokumentiert ist. Ich habe eine Stunde lang geraten, wie man Informationen aus verschachtelten Elementen herausholt. Ich wünschte, ich hätte dieses Beispiel gehabt, deshalb poste ich es hier, um anderen diesen Zeitverlust zu vermeiden (und das Beispiel ist zu lang für einen Kommentar).
Diemo

2

Mein Python-Skript xgrep.py macht genau das. Um nach allen Attributen attributevon Elementen elementin Dateien zu suchen filename.xml ..., führen Sie diese wie folgt aus:

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

Es gibt verschiedene Schalter zum Steuern der Ausgabe, z. B. -czum Zählen von Übereinstimmungen, -izum Einrücken der übereinstimmenden Teile und-l zum Ausgeben von Dateinamen.

Das Skript ist nicht als Debian- oder Ubuntu-Paket verfügbar, aber alle seine Abhängigkeiten sind.


Und Sie hosten auf Sourcehut! Nett!
Clacke

1

Da dieses Projekt anscheinend ziemlich neu ist, lesen Sie https://github.com/jeffbr13/xq , es scheint ein Wrapper zu sein lxml, aber das ist alles, was Sie wirklich brauchen (und Ad-hoc-Lösungen mit lxml auch in anderen Antworten zu veröffentlichen).


1

Ich war mit Python-Einzeilern für HTML XPath-Abfragen nicht zufrieden, also habe ich meine eigenen geschrieben. Angenommen, Sie haben das python-lxmlPaket installiert oder ausgeführt 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 }

Sobald Sie es haben, können Sie es wie in diesem Beispiel verwenden:

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

0

Installieren Sie die BaseX- Datenbank und verwenden Sie dann den "Standalone-Befehlszeilenmodus" wie folgt :

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

oder

basex -i filename.xml //element@attribute

Die Abfragesprache ist eigentlich XQuery (3.0), nicht XPath. Da XQuery jedoch eine Obermenge von XPath ist, können Sie XPath-Abfragen verwenden, ohne es jemals zu bemerken.

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.