Jak wykonać jednowierszowe XPath z powłoki?

192

Czy jest dostępny pakiet dla Ubuntu i / lub CentOS, który ma narzędzie wiersza poleceń, które może wykonywać jedno-liniowy XPath, taki jak foo //element@attribute filename.xmllub, foo //element@attribute < filename.xmli zwracać wyniki linia po linii?

Szukam czegoś, co pozwoliłoby mi tylko apt-get install fooalbo yum install fooi po prostu działa out-of-the-box, bez opakowania lub inna adaptacja konieczne.

Oto kilka przykładów zbliżających się spraw:

Nokogiri. Jeśli napiszę to opakowanie, mógłbym wywołać opakowanie w sposób opisany powyżej:

#!/usr/bin/ruby

require 'nokogiri'

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

XML :: XPath. Działa z tym opakowaniem:

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

xpathz XML :: XPath zwraca zbyt dużo hałasu, -- NODE --a attribute = "value".

xml_grep z XML :: Twig nie obsługuje wyrażeń, które nie zwracają elementów, dlatego nie można go użyć do wyodrębnienia wartości atrybutów bez dalszego przetwarzania.

EDYTOWAĆ:

echo cat //element/@attribute | xmllint --shell filename.xmlzwraca hałas podobny do xpath.

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

xmllint --xpath 'string(//element/@attribute)' filename.xml zwraca to, czego chcę, ale tylko dla pierwszego meczu.

Dla innego rozwiązania, które prawie spełnia pytanie, oto XSLT, którego można użyć do oceny dowolnych wyrażeń XPath (wymaga obsługi dyn: Evaluation w procesorze 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>

Uruchom z xsltproc --stringparam pattern //element/@attribute --stringparam value . arbitrary-xpath.xslt filename.xml.

clacke
źródło
+1 za dobre pytanie i burzę mózgów na temat znalezienia prostego i niezawodnego sposobu drukowania wielu wyników na nowej linii
Gilles Quenot
1
Zauważ, że „szum” z xpathjest na STDERR, a nie na STDOUT.
miken32
@ miken32 Nie. Chciałem tylko wartość wyjściową. hastebin.com/ekarexumeg.bash
clacke

Odpowiedzi:

271

Powinieneś spróbować tych narzędzi:

  • xmlstarlet : można edytować, wybierać, przekształcać ... Domyślnie nie jest instalowany, xpath1
  • xmllint : często instalowane domyślnie z libxml2-utils , xpath1 (sprawdź moje opakowanie, aby --xpathwłączyć bardzo stare wersje i znaki nowej linii rozdzielane (v <2.9.9)
  • xpath: zainstalowany przez moduł Perla XML::XPath, xpath1
  • xml_grep: zainstalowany przez moduł Perla XML::Twig, xpath1 (ograniczone użycie xpath)
  • xidel: xpath3
  • saxon-lint : mój własny projekt, opakowanie nad biblioteką Java Saxon-HE @ Michaela Kaya, xpath3

xmllintpochodzi z libxml2-utils(może być używany jako interaktywna powłoka z--shell przełącznikiem)

xmlstarlet jest xmlstarlet .

xpath pochodzi z modułem Perla XML::Xpath

xml_grep pochodzi z modułem Perla XML::Twig

xidel jest xidel

saxon-lintużywając SaxonHE 9.6 , XPath 3.x (+ kompatybilność retro)

Np .:

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
źródło
7
Doskonały! xmlstarlet sel -T -t -m '//element/@attribute' -v '.' -n filename.xmlrobi dokładnie to, czego chcę!
clacke
2
Uwaga: podobno xmlstarlet został porzucony, ale teraz jest ponownie w fazie rozwoju.
clacke
6
Uwaga: Niektóre starsze wersje xmllintnie obsługują argumentu wiersza poleceń --xpath, ale większość wydaje się wspierać --shell. Nieznacznie brudniejsze wyjście, ale nadal przydatne w oprawie.
kevinarpe
Nadal mam problemy z zapytaniem o zawartość węzła, a nie atrybut. Czy ktoś może podać na to przykład? Z jakiegoś powodu wciąż trudno mi znaleźć xmlstarlet i znaleźć właściwy sposób dopasowania, wartości, rootowania, aby wyświetlić strukturę dokumentu itp. Nawet z pierwszym sel -t -m ... -v ...przykładem z tej strony: arstechnica.com/information-technology/2005 / 11 / linux-20051115/2 , dopasowując wszystkie oprócz ostatniego węzła i zapisując ten jeden dla wyrażenia wartości, takiego jak mój przypadek użycia, nadal nie mogę go uzyskać, po prostu otrzymuję puste dane wyjściowe ..
Pysis
fajna wersja
Xpath
20

Możesz także wypróbować mój Xidel . Nie ma go w pakiecie w repozytorium, ale można go po prostu pobrać ze strony internetowej (nie ma zależności).

Ma prostą składnię do tego zadania:

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

Jest to jedno z rzadkich narzędzi, które obsługują XPath 2.

BeniBela
źródło
2
Xidel wygląda całkiem fajnie, chociaż prawdopodobnie powinieneś wspomnieć, że jesteś także autorem tego narzędzia, które polecasz.
FrustratedWithFormsDesigner
1
Saxon i saxon-lint używają xpath3;)
Gilles Quenot 25.09.16
Xidel (0..8.win32.zip) pokazuje się jako posiadający złośliwe oprogramowanie na Virustotal. Spróbuj więc na własne ryzyko virustotal.com/#/file/…
JGFMK
świetnie - dodam xidel do mojej osobistej skrzynki na
klucze
15

Jest już jeden pakiet, który najprawdopodobniej zostanie zainstalowany w systemie python-lxml. Jeśli tak, jest to możliwe bez instalowania dodatkowego pakietu:

python -c "from lxml.etree import parse; from sys import stdin; print '\n'.join(parse(stdin).xpath('//element/@attribute'))"
clacke
źródło
1
Jak przekazać nazwę pliku?
Ramakrishnan Kannan
4
To działa na stdin. Eliminuje to potrzebę włączenia open()i close()już dość długiej jedno-liniowej. Aby przeanalizować plik, po prostu uruchom python -c "from lxml.etree import parse; from sys import stdin; print '\n'.join(parse(stdin).xpath('//element/@attribute'))" < my_file.xmli pozwól powłoce obsługiwać wyszukiwanie, otwieranie i zamykanie pliku.
clacke
10

W moich zapytaniach dotyczących plików maven pom.xml natknąłem się na to pytanie. Miałem jednak następujące ograniczenia:

  • musi działać na różnych platformach.
  • musi istnieć we wszystkich głównych dystrybucjach Linuksa bez dodatkowej instalacji modułu
  • musi obsługiwać złożone pliki XML, takie jak pliki maven pom.xml
  • prosta składnia

Próbowałem wielu z powyższych bez powodzenia:

  • python lxml.etree nie jest częścią standardowej dystrybucji python
  • Plik xml.etree jest, ale nie radzi sobie dobrze ze złożonymi plikami maven pom.xml, nie zagłębiłem się wystarczająco głęboko
  • Python xml.etree nie obsługuje plików maven pom.xml z nieznanego powodu
  • xmllint też nie działa, zrzuty pamięci często na Ubuntu 12.04 "xmllint: przy użyciu libxml w wersji 20708"

Rozwiązaniem, które natknąłem się na to, że jest stabilne, krótkie i działa na wielu platformach i jest dojrzałe, jest rexml lib wbudowany w Ruby:

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

Inspiracją do znalezienia tego były następujące artykuły:

Mikrofon
źródło
1
To nawet węższe kryteria niż pytanie, więc zdecydowanie pasuje jako odpowiedź. Jestem pewien, że wielu osobom, które wpadły na twoją sytuację, pomogą twoje badania. Trzymam xmlstarletjako przyjętą odpowiedź, ponieważ pasuje ona do moich szerszych kryteriów i jest naprawdę fajna . Ale prawdopodobnie od czasu do czasu będę używał twojego rozwiązania.
clacke
2
Dodałbym, że aby uniknąć cudzysłowu wokół wyniku , użyj putszamiast pw poleceniu Ruby.
TomG
10

Saxon zrobi to nie tylko dla XPath 2.0, ale także dla XQuery 1.0 i (w wersji komercyjnej) 3.0. Nie jest dostarczany jako pakiet Linux, ale jako plik jar. Składnia (którą można łatwo zawinąć w prosty skrypt) to

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

AKTUALIZACJA 2020

Saxon 10.0 zawiera narzędzie Gizmo, którego można używać interaktywnie lub wsadowo z wiersza poleceń. Na przykład

java net.sf.saxon.Gizmo -s:source.xml
/>show //element/@attribute
/>quit
Michael Kay
źródło
SaxonB jest w pakiecie Ubuntu, libsaxonb-javaale jeśli uruchomię saxonb-xquery -qs://element/@attribute -s:filename.xml, dostaję SENR0001: Cannot serialize a free-standing attribute nodetaki sam problem jak z np xml_grep.
clacke
3
Jeśli chcesz zobaczyć pełne szczegóły węzła atrybutu wybranego przez to zapytanie, użyj opcji -wrap w wierszu poleceń. Jeśli chcesz tylko wartość ciągu atrybutu, dodaj / string () do zapytania.
Michael Kay
Dzięki. Dodawanie / string () zbliża się. Ale generuje nagłówek XML i umieszcza wszystkie wyniki w jednym wierszu, więc nadal nie ma cygara.
clacke
2
Jeśli nie chcesz nagłówka XML, dodaj opcję! Method = text.
Michael Kay
Aby użyć przestrzeni nazw, dodaj ją w -qsnastępujący sposób:'-qs:declare namespace mets="http://www.loc.gov/METS/";/mets:mets/mets:dmdSec'
igo
5

Możesz być także zainteresowany xsh . Posiada tryb interaktywny, w którym możesz robić co tylko chcesz z dokumentem:

open 1.xml ;
ls //element/@id ;
for //p[@class="first"] echo text() ;
choroba
źródło
Nie wydaje się być dostępny jako pakiet, przynajmniej nie w Ubuntu.
clacke
1
@clacke: Nie jest, ale można go zainstalować z CPAN przez cpan XML::XSH2.
choroba
@choroba, próbowałem tego na OS X, ale nie udało się zainstalować, z pewnym błędem makefile.
cnst
@cnst: Czy masz zainstalowany XML :: LibXML?
choroba
@choroba, nie wiem; ale chodzi mi o to, że cpan XML::XSH2nic nie instaluje.
cnst
5

Odpowiedź clacke jest świetna, ale myślę, że działa tylko wtedy, gdy źródłem jest dobrze sformatowany XML, a nie normalny HTML.

Aby zrobić to samo w przypadku normalnej zawartości sieci Web - dokumenty HTML, które niekoniecznie są dobrze sformatowanymi danymi XML:

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

I zamiast tego użyj html5lib (aby upewnić się, że otrzymujesz takie samo zachowanie podczas analizowania jak przeglądarki internetowe - ponieważ podobnie jak parsery przeglądarek, html5lib spełnia wymagania parsowania w specyfikacji 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'))
sidehowbarker
źródło
Tak, zakochałem się w moim założeniu, że XPath oznacza XML. Ta odpowiedź jest dobrym uzupełnieniem innych tutaj i dziękuję za poinformowanie mnie o html5lib!
clacke
3

Podobnie do odpowiedzi Mike'a i Clacke'a, tutaj jest jedno-liniowy python (przy użyciu python> = 2.5), aby uzyskać wersję kompilacji z pliku pom.xml, który omija fakt, że pliki pom.xml zwykle nie mają dtd lub domyślna przestrzeń nazw, więc nie wydają się być poprawnie sformułowane w 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)"

Testowany na komputerach Mac i Linux i nie wymaga instalowania żadnych dodatkowych pakietów.

pdr
źródło
2
Użyłem tego dzisiaj! Nasze serwery kompilacji nie miały lxmlani xmllint, ani nawet Ruby. W duchu formatu we własnej odpowiedzi napisałem go jak python3 -c "from xml.etree.ElementTree import parse; from sys import stdin; print(parse(stdin).find('.//element[subelement=\"value\"]/othersubelement').text)" <<< "$variable_containing_xml"w bash. .getroot()nie wydaje się konieczne.
clacke
2

Oprócz XML :: XSH i XML :: XSH2 istnieją pewne greppodobne narzędzia ssące jako App::xml_grep2i XML::Twig(które zawierają xml_grepraczej niż xml_grep2). Mogą one być bardzo przydatne podczas pracy z dużymi lub licznymi plikami XML dla szybkich onelinerów lub Makefilecelów. XML::Twigjest szczególnie miło pracować z perlpodejściem skryptowym, gdy chcesz nieco więcej przetwarzania niż twoje $SHELLi xmllint xstlproczaoferować.

Schemat numerowania w nazwach aplikacji wskazuje, że wersje „2” są nowszą / późniejszą wersją zasadniczo tego samego narzędzia, które może wymagać późniejszych wersji innych modułów (lub perlsamego).

G. Cito
źródło
xml_grep2 -t //element@attribute filename.xmldziała i robi to, czego oczekuję ( xml_grep --root //element@attribute --text_only filename.xmlwciąż nie, zwraca błąd „nierozpoznane wyrażenie”). Wspaniały!
clacke
Co xml_grep --pretty_print --root '//element[@attribute]' --text_only filename.xml? Nie jestem pewien, co się tam dzieje ani o czym mówi XPath []w tym przypadku, ale otaczanie @attributenawiasami kwadratowymi działa dla xml_grepi xml_grep2.
G. Cito,
To znaczy //element/@attributenie //element@attribute. Najwyraźniej nie można go edytować, ale pozostawiając go tam zamiast usuwać + zamieniać, aby nie pomylić historii tej dyskusji.
clacke
//element[@attribute]wybiera elementy typu, elementktóre mają atrybut attribute. Nie chcę elementu, tylko atrybut. <element attribute='foo'/>powinien mi dać foo, a nie pełny <element attribute='foo'/>.
clacke
... i --text_onlyw tym kontekście daje mi pusty ciąg w przypadku elementu takiego jak <element attribute='foo'/>bez węzła tekstowego w środku.
clacke
2

Należy wspomnieć, że sam nokogiri jest dostarczany z narzędziem wiersza poleceń, które należy zainstalować gem install nokogiri.

Można znaleźć tego blogu użyteczne .

Geoff Nixon
źródło
2

Wypróbowałem kilka narzędzi XPath z wiersza poleceń i kiedy zdałem sobie sprawę, że spędzam zbyt dużo czasu na wyszukiwaniu i zastanawianiu się, jak one działają, napisałem więc najprostszy możliwy parser XPath w Pythonie, który zrobił to, czego potrzebowałem.

Poniższy skrypt pokazuje wartość ciągu, jeśli wyrażenie XPath przekształca się w ciąg, lub pokazuje cały podwęzeł XML, jeśli wynikiem jest węzeł:

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

Wykorzystuje lxml- szybki parser XML napisany w C, który nie jest zawarty w standardowej bibliotece Pythona. Zainstaluj za pomocą pip install lxml. W systemie Linux / OSX może wymagać prefiksu sudo.

Stosowanie:

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

lxml może również akceptować adres URL jako dane wejściowe:

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

Wyodrębnij atrybut url w węźle obudowy, tj . <enclosure url="http:...""..>):

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

Xpath w Google Chrome

Jako niepowiązana uwaga dodatkowa: jeśli przypadkiem chcesz uruchomić wyrażenie XPath wobec znaczników strony internetowej, możesz to zrobić bezpośrednio z narzędzi devtools Chrome: kliknij prawym przyciskiem myszy stronę w Chrome> wybierz Sprawdź, a następnie w DevTools konsola wklej wyrażenie XPath jako $x("//spam/eggs").

Pobierz wszystkich autorów na tej stronie:

$x("//*[@class='user-details']/a/text()")
ccpizza
źródło
Ani jednego-liner, a lxmljuż wspomniano w dwóch innych odpowiedziach lat zanim same.
clacke
2

Oto jeden przypadek użycia xmlstarlet do wyodrębnienia danych z elementów zagnieżdżonych elem1, elem2 do jednego wiersza tekstu z tego typu XML (pokazujący również, jak obsługiwać przestrzenie nazw):

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

Wyjście będzie

0.586 10.586 cue-in outro

W tym fragmencie -m dopasowuje zagnieżdżony elem2, -v wyświetla wartości atrybutów (z wyrażeniami i względnym adresowaniem), -o dosłowny tekst, -n dodaje nowy wiersz:

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

Jeśli potrzeba więcej atrybutów z elem1, można to zrobić w następujący sposób (pokazując również funkcję 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

Zwróć uwagę na (niepotrzebne IMO) komplikacje z przestrzeniami nazw (ns, zadeklarowane z -N), które prawie mnie poddały na xpath i xmlstarlet, i napisałem szybki konwerter ad-hoc.

diemo
źródło
xmlstarlet jest świetny, ale o tym już wspomina zaakceptowana i główna odpowiedź w rankingu. Informacje o tym, jak obsługiwać przestrzenie nazw, mogą być przydatne jako komentarz, jeśli w ogóle. Każdy, kto ma problemy z przestrzeniami nazw i xmlstarlet, może znaleźć doskonałą dyskusję w dokumentacji
clacke
2
Oczywiście, @clacke, xmlstarlet zostało wspomniane kilka razy, ale także, że trudno jest je uchwycić i nie jest ono dokumentowane. Przez godzinę zgadywałam, jak uzyskać informacje z zagnieżdżonych elementów. Chciałbym mieć ten przykład, dlatego zamieszczam go tutaj, aby uniknąć straty czasu przez innych (a przykład jest za długi na komentarz).
diemo
2

Mój skrypt xgrep.py w Pythonie właśnie to robi. Aby wyszukać wszystkie atrybuty attributeelementów elementw plikach filename.xml ..., uruchom go w następujący sposób:

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

Istnieją różne przełączniki do sterowania danymi wyjściowymi, na przykład -cdo zliczania dopasowań, -ido wcięcia pasujących części i -ldo wysyłania tylko nazw plików.

Skrypt nie jest dostępny jako pakiet Debian lub Ubuntu, ale wszystkie jego zależności są.

Andreas Nolda
źródło
I hostujesz na sourcehut! Miły!
clacke,
1

Ponieważ ten projekt jest pozornie nowy, sprawdź https://github.com/jeffbr13/xq , wydaje się to być otoczeniem lxml, ale to wszystko, czego naprawdę potrzebujesz (i opublikowałem rozwiązania ad hoc przy użyciu lxml również w innych odpowiedziach)

mgrandi
źródło
1

Nie byłem zadowolony z jedno-liniowych zapytań Python do zapytań HTML XPath, więc napisałem własny. Zakłada, że ​​zainstalowałeś python-lxmlpakiet lub uruchomiłeś 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 }

Gdy go masz, możesz użyć go tak jak w tym przykładzie:

> curl -s https://slashdot.org | htmlxpath '//title/text()'
Slashdot: News for nerds, stuff that matters
d33tah
źródło
0

Zainstaluj bazę danych BaseX , a następnie użyj „samodzielnego trybu wiersza polecenia” w następujący sposób:

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

lub

basex -i filename.xml //element@attribute

Językiem zapytań jest w rzeczywistości XQuery (3.0), a nie XPath, ale ponieważ XQuery jest nadzbiorem XPath, można używać zapytań XPath bez uprzedzenia.

igneus
źródło