Come eseguire XPath one-liner dalla shell?


192

Esiste un pacchetto là fuori, per Ubuntu e / o CentOS, che ha uno strumento da riga di comando in grado di eseguire un one-liner XPath come foo //element@attribute filename.xmlo foo //element@attribute < filename.xmlrestituire i risultati riga per riga?

Sto cercando qualcosa che mi permettesse di solo apt-get install fooo yum install fooe poi solo funziona out-of-the-box, non gli involucri o altri adattamento necessario.

Ecco alcuni esempi di cose che si avvicinano:

Nokogiri. Se scrivo questo wrapper potrei chiamare il wrapper nel modo sopra descritto:

#!/usr/bin/ruby

require 'nokogiri'

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

XML :: XPath. Funzionerebbe con questo 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");
}

xpathda XML :: XPath restituisce troppo rumore -- NODE --e attribute = "value".

xml_grep da XML :: Twig non può gestire espressioni che non restituiscono elementi, quindi non può essere utilizzato per estrarre i valori degli attributi senza ulteriore elaborazione.

MODIFICARE:

echo cat //element/@attribute | xmllint --shell filename.xmlrestituisce un rumore simile a xpath.

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

xmllint --xpath 'string(//element/@attribute)' filename.xml restituisce quello che voglio, ma solo per la prima partita.

Per un'altra soluzione che soddisfa quasi la domanda, ecco un XSLT che può essere utilizzato per valutare espressioni XPath arbitrarie (richiede dyn: valuta il supporto nel processore 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>

Corri con xsltproc --stringparam pattern //element/@attribute --stringparam value . arbitrary-xpath.xslt filename.xml.


+1 per una buona domanda e per il brainstorming sulla ricerca di un modo semplice e affidabile per stampare più risultati ciascuno su una nuova riga
Gilles Quenot

1
Si noti che il "rumore" proviene xpathda STDERR e non da STDOUT.
miken32

@ miken32 No. Volevo solo il valore per l'output. hastebin.com/ekarexumeg.bash
clacke

Risposte:


271

Dovresti provare questi strumenti:

  • xmlstarlet : può modificare, selezionare, trasformare ... Non installato per impostazione predefinita, xpath1
  • xmllint: spesso installato di default con libxml2-utils, xpath1 (controlla il mio wrapper per avere --xpathsu versioni molto vecchie e output delimitato da newline (v <2.9.9)
  • xpath: installato tramite il modulo di perl XML::XPath, xpath1
  • xml_grep: installato tramite il modulo di perl XML::Twig, xpath1 (utilizzo xpath limitato)
  • xidel: xpath3
  • saxon-lint : mio progetto, wrapper sulla libreria Java Saxon-HE di @Michael Kay, xpath3

xmllintviene fornito libxml2-utils(può essere utilizzato come shell interattiva con l' --shellinterruttore)

xmlstarletlo è xmlstarlet.

xpath viene fornito con il modulo di perl XML::Xpath

xml_grep viene fornito con il modulo di perl XML::Twig

xidel è xidel

saxon-lintusando SaxonHE 9.6 , XPath 3.x (+ retro compatibilità)

Es:

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
Eccellente! xmlstarlet sel -T -t -m '//element/@attribute' -v '.' -n filename.xmlfa esattamente quello che voglio!
Clacke,

2
Nota: si diceva che xmlstarlet fosse abbandonato, ma ora è di nuovo in fase di sviluppo attivo.
clacchio

6
Nota: alcune versioni precedenti di xmllintnon supportano l'argomento della riga di comando --xpath, ma la maggior parte sembra supportare --shell. Uscita leggermente più sporca, ma comunque utile in un legame.
Kevinevpe,

Mi sembra ancora di avere problemi a interrogare il contenuto del nodo, non un attributo. Qualcuno può fornire un esempio per questo? Per qualche motivo, trovo ancora xmlstarlet difficile da capire e trovare la corrispondenza tra corrispondenza, valore, root per visualizzare solo la struttura del documento, ecc. Anche con il primo sel -t -m ... -v ...esempio di questa pagina: arstechnica.com/information-technology/2005 / 11 / linux-20051115/2 , abbinando tutti tranne l'ultimo nodo e salvando quello per l'espressione di valore come il mio caso d'uso, non riesco ancora a ottenerlo, ho solo un output vuoto ..
Pysis

bello sulla versione di xpath - mi sarei appena imbattuto in questa limitazione del xmllint altrimenti eccellente
JonnyRaa

20

Puoi anche provare il mio Xidel . Non si trova in un pacchetto nel repository, ma è possibile scaricarlo dalla pagina Web (non ha dipendenze).

Ha una sintassi semplice per questa attività:

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

Ed è uno dei rari di questi strumenti che supporta XPath 2.


2
Xidel ha un bell'aspetto, anche se probabilmente dovresti menzionare che sei anche l'autore di questo strumento che consigli.
FrustratedWithFormsDesigner,

1
Saxon e saxon-lint usano xpath3;)
Gilles Quenot il

Xidel (0..8.win32.zip) risulta avere malware su Virustotal. Quindi prova a tuo rischio e pericolo virustotal.com/#/file/…
JGFMK

fantastico - Aggiungerò xidel alla mia cassetta degli attrezzi per chiavi personali
maoizm

15

Un pacchetto che molto probabilmente sarà già installato su un sistema è python-lxml. In tal caso, ciò è possibile senza installare alcun pacchetto aggiuntivo:

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

1
Come passare il nome file?
Ramakrishnan Kannan,

4
Questo funziona stdin. Ciò elimina la necessità di includere open()e close()in un rivestimento già abbastanza lungo. Per analizzare un file basta eseguirlo python -c "from lxml.etree import parse; from sys import stdin; print '\n'.join(parse(stdin).xpath('//element/@attribute'))" < my_file.xmle lasciare che la shell gestisca la ricerca, l'apertura e la chiusura del file.
clacke,

10

Nella mia ricerca per interrogare i file pom.xml di Maven ho corso attraverso questa domanda. Tuttavia ho avuto le seguenti limitazioni:

  • deve essere eseguito su più piattaforme.
  • deve esistere su tutte le principali distribuzioni di Linux senza alcuna installazione aggiuntiva del modulo
  • deve gestire file xml complessi come file pom.xml di maven
  • sintassi semplice

Ho provato molti dei precedenti senza successo:

  • python lxml.etree non fa parte della distribuzione standard di Python
  • xml.etree è ma non gestisce bene i file pom.xml di maven complessi, non è stato scavato abbastanza in profondità
  • python xml.etree non gestisce i file pom.xml di Maven per motivi sconosciuti
  • xmllint non funziona neanche, core dump spesso su Ubuntu 12.04 "xmllint: usando la versione 20708 di libxml"

La soluzione che ho trovato stabile, breve e funzionante su molte piattaforme e che è matura è la libreria rexml incorporata in ruby:

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

Ciò che mi ha ispirato a trovare questo è stato i seguenti articoli:


1
È un criterio ancora più ristretto rispetto alla domanda, quindi sicuramente si adatta come una risposta. Sono sicuro che molte persone che si sono imbattute nella tua situazione saranno aiutate dalla tua ricerca. Sto mantenendo xmlstarletla risposta accettata, perché si adatta ai miei criteri più ampi ed è davvero pulito . Ma di tanto in tanto avrò bisogno della tua soluzione.
clacke,

2
Vorrei aggiungere che per evitare le virgolette intorno al risultato , utilizzare putsinvece che pnel comando Ruby.
tooomg

10

Saxon lo farà non solo per XPath 2.0, ma anche per XQuery 1.0 e (nella versione commerciale) 3.0. Non viene fornito come pacchetto Linux, ma come file jar. La sintassi (che puoi facilmente avvolgere in un semplice script) è

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

AGGIORNAMENTO 2020

Saxon 10.0 include lo strumento Gizmo, che può essere utilizzato in modo interattivo o in batch dalla riga di comando. Per esempio

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

SaxonB è in Ubuntu, pacchetto libsaxonb-java, ma se corro saxonb-xquery -qs://element/@attribute -s:filename.xmlottengo lo SENR0001: Cannot serialize a free-standing attribute nodestesso problema con es xml_grep.
clacchio

3
Se si desidera visualizzare i dettagli completi del nodo dell'attributo selezionato da questa query, utilizzare l'opzione -wrap sulla riga di comando. Se si desidera solo il valore stringa dell'attributo, aggiungere / string () alla query.
Michael Kay,

Grazie. L'aggiunta di / string () si avvicina. Ma genera un'intestazione XML e mette tutti i risultati su una riga, quindi ancora nessun sigaro.
clacke

2
Se non si desidera un'intestazione XML, aggiungere l'opzione! Method = text.
Michael Kay

Per usare lo spazio dei nomi aggiungilo in -qsquesto modo:'-qs:declare namespace mets="http://www.loc.gov/METS/";/mets:mets/mets:dmdSec'
igo

5

Potresti anche essere interessato a xsh . È dotato di una modalità interattiva in cui puoi fare quello che vuoi con il documento:

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

Non sembra essere disponibile come pacchetto, almeno non in Ubuntu.
Clacke,

1
@clacke: non lo è, ma può essere installato da CPAN da cpan XML::XSH2.
Choroba,

@choroba, l'ho provato su OS X, ma l'installazione non è riuscita, con qualche errore di makefile.
CNST

@cnst: hai installato XML :: LibXML?
Choroba,

@choroba, non lo so; ma il mio punto è che cpan XML::XSH2non riesce a installare nulla.
CNST

5

La risposta di Clacke è ottima, ma penso che funzioni solo se la tua fonte è un XML ben formato, non un normale HTML.

Quindi, fare lo stesso per i normali contenuti Web: documenti HTML che non sono necessariamente XML ben formati:

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

E invece utilizzare html5lib (per assicurarsi di ottenere lo stesso comportamento di analisi dei browser Web, perché come i parser del browser, html5lib è conforme ai requisiti di analisi nelle specifiche 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'))

Sì, mi sono innamorato del mio presupposto nella domanda, che XPath implica XML. Questa risposta è un buon complemento agli altri qui, e grazie per avermi fatto conoscere html5lib!
Clacke,

3

Simile alle risposte di Mike e Clacke, ecco il one-liner di Python (usando python> = 2.5) per ottenere la versione build da un file pom.xml che aggira il fatto che i file pom.xml normalmente non hanno un dtd o spazio dei nomi predefinito, quindi non apparire ben formato in 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)"

Testato su Mac e Linux e non richiede l'installazione di pacchetti aggiuntivi.


2
L'ho usato oggi! I nostri server di build non avevano né lxmlxmllintné nemmeno Ruby. Nello spirito del formato nella mia risposta , l'ho scritto come 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()non sembra necessario.
clacke il

2

Oltre a XML :: XSH e XML :: XSH2 ci sono alcune greputilità simili che fanno schifo come App::xml_grep2e XML::Twig(che include xml_greppiuttosto che xml_grep2). Questi possono essere molto utili quando si lavora su file XML di grandi dimensioni o numerosi per Makefileobiettivi o obiettivi rapidi . XML::Twigè particolarmente bello lavorare con un perlapproccio di scripting quando si desidera un po 'più di elaborazione $SHELLe xmllint xstlprocofferta.

Lo schema di numerazione nei nomi dell'applicazione indica che le versioni "2" sono la versione più recente / successiva essenzialmente dello stesso strumento che potrebbe richiedere versioni successive di altri moduli (o di per perlsé).


xml_grep2 -t //element@attribute filename.xmlfunziona e fa quello che mi aspetto ( xml_grep --root //element@attribute --text_only filename.xmlnon lo fa, restituisce un errore "espressione non riconosciuta"). Grande!
clacke

Che dire xml_grep --pretty_print --root '//element[@attribute]' --text_only filename.xml? Non sono sicuro di quello che sta succedendo lì o di quello che XPath dice []in questo caso, ma che circonda un @attributeparentesi quadre funziona per xml_grepe xml_grep2.
G. Cito

Voglio dire //element/@attribute, no //element@attribute. Apparentemente non puoi modificarlo, ma lasciandolo lì piuttosto che cancellare + sostituisci per non confondere la storia di questa discussione.
clacke

//element[@attribute]seleziona elementi di tipo elementche hanno un attributo attribute. Non voglio l'elemento, solo l'attributo. <element attribute='foo'/>dovrebbe darmi foo, non il pieno <element attribute='foo'/>.
clacke

... e --text_onlyin quel contesto mi dà la stringa vuota nel caso di un elemento come <element attribute='foo'/>senza nodo di testo all'interno.
clacke

2

Vale la pena ricordare che lo stesso nokogiri viene fornito con uno strumento da riga di comando, che dovrebbe essere installato con gem install nokogiri.

Potresti trovare utile questo post sul blog .


2

Ho provato un paio di utility XPath da riga di comando e quando mi sono reso conto che sto trascorrendo troppo tempo a cercare e capire come funzionano, quindi ho scritto il parser XPath più semplice possibile in Python che ha fatto quello che mi serviva.

Lo script seguente mostra il valore della stringa se l'espressione XPath restituisce una stringa o mostra l'intero nodo secondario XML se il risultato è un nodo:

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

Usa lxml- un veloce parser XML scritto in C che non è incluso nella libreria standard di Python. Installalo con pip install lxml. Su Linux / OSX potrebbe essere necessario il prefisso con sudo.

Uso:

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

lxml può anche accettare un URL come input:

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

Estrarre l'attributo url sotto un nodo di enclosure, ovvero <enclosure url="http:...""..>):

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

Xpath in Google Chrome

Come nota a margine non correlata: se per caso vuoi eseguire un'espressione XPath contro il markup di una pagina web, puoi farlo direttamente dai devtools di Chrome: fai clic con il pulsante destro del mouse sulla pagina in Chrome> seleziona Controlla, quindi in DevTools console incolla la tua espressione XPath come $x("//spam/eggs").

Ottieni tutti gli autori in questa pagina:

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

Non un one-liner, ed lxmlè già stato menzionato in altre due risposte anni prima della tua.
clacke il

2

Ecco un caso d'uso di xmlstarlet per estrarre dati da elementi nidificati elem1, elem2 su una riga di testo da questo tipo di XML (mostrando anche come gestire gli spazi dei nomi):

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

L'output sarà

0.586 10.586 cue-in outro

In questo frammento, -m corrisponde a elem2 nidificato, -v restituisce i valori degli attributi (con espressioni e indirizzamento relativo), -o testo letterale, -n aggiunge una nuova riga:

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

Se sono necessari più attributi da elem1, si può fare così (mostrando anche la funzione 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

Nota la complicazione (non necessaria dell'IMO) con gli spazi dei nomi (ns, dichiarati con -N), che mi ha fatto quasi rinunciare a xpath e xmlstarlet e a scrivere un rapido convertitore ad-hoc.


xmlstarlet è eccezionale, ma la risposta accettata e in classifica principale lo menziona già. Le informazioni su come gestire gli spazi dei nomi potrebbero essere state rilevanti come commento, se non del tutto. Chiunque incontri problemi con namespace e xmlstarlet può trovare una discussione
clacke,

2
Certo, @clacke, xmlstarlet è stato menzionato più volte, ma è anche difficile da capire e non documentato. Per un'ora stavo indovinando come ottenere informazioni dagli elementi nidificati. Vorrei aver avuto quell'esempio, ecco perché lo sto pubblicando qui per evitare ad altri quella perdita di tempo (e l'esempio è troppo lungo per un commento).
diemo,

2

Il mio script Python xgrep.py fa esattamente questo. Per cercare tutti gli attributi attributedegli elementi elementnei file filename.xml ..., eseguirli come segue:

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

Esistono vari interruttori per il controllo dell'uscita, ad esempio -cper il conteggio delle corrispondenze, -iper il rientro delle parti corrispondenti e-l per l'output dei soli nomi di file.

Lo script non è disponibile come pacchetto Debian o Ubuntu, ma tutte le sue dipendenze lo sono.


E stai ospitando su sourcehut! Bello!
clacchio

1

Poiché questo progetto è apparentemente abbastanza nuovo, dai un'occhiata a https://github.com/jeffbr13/xq , sembra essere un wrapper lxml, ma è tutto ciò di cui hai davvero bisogno (e ha pubblicato soluzioni ad hoc usando lxml anche in altre risposte)


1

Non ero contento delle linee guida di Python per le query HTML XPath, quindi ho scritto il mio. Presuppone che tu abbia installato il python-lxmlpacchetto o eseguito 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 }

Una volta che lo hai, puoi usarlo come in questo esempio:

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

0

Installare il database BaseX , quindi utilizzare la "modalità riga di comando autonoma" in questo modo:

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

o

basex -i filename.xml //element@attribute

Il linguaggio di query è in realtà XQuery (3.0), non XPath, ma poiché XQuery è un superset di XPath, è possibile utilizzare le query XPath senza mai accorgersene.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.