Bagaimana cara menjalankan XPath one-liners dari shell?

192

Apakah ada paket di luar sana, untuk Ubuntu dan / atau CentOS, yang memiliki alat baris perintah yang dapat menjalankan XPath one-liner seperti foo //element@attribute filename.xmlatau foo //element@attribute < filename.xmldan mengembalikan hasil baris demi baris?

Saya mencari sesuatu yang akan memungkinkan saya untuk hanya apt-get install fooatau yum install foodan kemudian hanya bekerja di luar kotak, tidak ada pembungkus atau adaptasi lain yang diperlukan.

Berikut adalah beberapa contoh hal yang mendekati:

Nokogiri. Jika saya menulis bungkus ini saya bisa memanggil bungkusnya dengan cara yang dijelaskan di atas:

#!/usr/bin/ruby

require 'nokogiri'

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

XML :: XPath. Akan bekerja dengan bungkus ini:

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

xpathdari XML :: XPath mengembalikan terlalu banyak noise, -- NODE --dan attribute = "value".

xml_grep dari XML :: Twig tidak dapat menangani ekspresi yang tidak mengembalikan elemen, jadi tidak dapat digunakan untuk mengekstrak nilai atribut tanpa diproses lebih lanjut.

EDIT:

echo cat //element/@attribute | xmllint --shell filename.xmlmengembalikan noise yang mirip dengan xpath.

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

xmllint --xpath 'string(//element/@attribute)' filename.xml mengembalikan apa yang saya inginkan, tetapi hanya untuk pertandingan pertama.

Untuk solusi lain yang hampir memuaskan pertanyaan, berikut adalah XSLT yang dapat digunakan untuk mengevaluasi ekspresi XPath yang berubah-ubah (memerlukan dyn: evaluasi dukungan dalam prosesor 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>

Jalankan dengan xsltproc --stringparam pattern //element/@attribute --stringparam value . arbitrary-xpath.xslt filename.xml.

klak
sumber
+1 untuk pertanyaan yang bagus dan untuk brainstorming tentang menemukan cara yang sederhana dan dapat diandalkan untuk mencetak beberapa hasil masing-masing pada baris baru
Gilles Quenot
1
Perhatikan bahwa "noise" dari xpathberada pada STDERR dan bukan STDOUT.
miken32
@ miken32 Tidak. Saya hanya menginginkan nilai untuk output. hastebin.com/ekarexumeg.bash
clacke

Jawaban:

271

Anda harus mencoba alat ini:

  • xmlstarlet : dapat mengedit, memilih, mengubah ... Tidak diinstal secara default, xpath1
  • xmllint: sering diinstal secara default dengan libxml2-utils, xpath1 (periksa pembungkus saya untuk --xpathmengaktifkan rilis yang sangat lama dan keluaran terbatas baris baru (v <2.9.9)
  • xpath: diinstal melalui modul perl XML::XPath, xpath1
  • xml_grep: diinstal melalui modul perl XML::Twig, xpath1 (penggunaan xpath terbatas)
  • xidel: xpath3
  • saxon-lint : proyek saya sendiri, membungkus perpustakaan Java Saxon-HE @ Michael Michael, xpath3

xmllintdilengkapi dengan libxml2-utils(dapat digunakan sebagai shell interaktif dengan --shellsakelar)

xmlstarletadalah xmlstarlet.

xpath dilengkapi dengan modul perl XML::Xpath

xml_grep dilengkapi dengan modul perl XML::Twig

xidel adalah xidel

saxon-lintmenggunakan SaxonHE 9.6 , XPath 3.x (+ kompatibilitas retro)

Mis:

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
sumber
7
Luar biasa! xmlstarlet sel -T -t -m '//element/@attribute' -v '.' -n filename.xmlmelakukan persis apa yang saya inginkan!
clacke
2
Catatan: xmlstarlet dikabarkan ditinggalkan, tetapi sekarang sedang dalam pengembangan lagi.
clacke
6
Catatan: Beberapa versi lawas xmllinttidak mendukung argumen baris perintah --xpath, tetapi sebagian besar tampaknya mendukung --shell. Output kotor sedikit, tetapi masih berguna dalam ikatan.
kevinarpe
Saya tampaknya masih mengalami masalah untuk menanyakan konten node, bukan atribut. Adakah yang bisa memberikan contoh untuk itu? Untuk beberapa alasan, saya masih menemukan xmlstarlet sulit untuk dipecahkan dan mendapatkan yang benar antara pencocokan, nilai, root untuk hanya melihat struktur dokumen, dan lain-lain. Bahkan dengan sel -t -m ... -v ...contoh pertama dari halaman ini: arstechnica.com/information-technology/2005 / 11 / linux-20051115/2 , cocok dengan semua kecuali node terakhir dan menyimpan satu untuk ekspresi nilai seperti kasus penggunaan saya, saya masih tidak bisa mendapatkannya, saya hanya mendapatkan output kosong ..
Pysis
yang bagus pada versi xpath - Saya baru saja mengalami keterbatasan xmllint yang luar biasa bagus
JonnyRaa
20

Anda juga dapat mencoba Xidel saya . Itu tidak ada dalam paket di repositori, tetapi Anda bisa mengunduhnya dari halaman web (tidak memiliki dependensi).

Ini memiliki sintaks sederhana untuk tugas ini:

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

Dan itu adalah salah satu alat yang langka yang mendukung XPath 2.

BeniBela
sumber
2
Xidel terlihat sangat keren, meskipun Anda mungkin harus menyebutkan bahwa Anda juga pembuat alat ini yang Anda rekomendasikan.
FrustratedWithFormsDesigner
1
Saxon dan saxon-lint menggunakan xpath3;)
Gilles Quenot
Xidel (0..8.win32.zip) terlihat memiliki malware di Virustotal. Jadi coba risiko Anda sendiri virustotal.com/#/file/…
JGFMK
hebat - saya akan menambahkan xidel ke kotak alat kunci pas pribadi saya
maoizm
15

Satu paket yang sangat mungkin untuk diinstal pada sistem sudah adalah python-lxml. Jika demikian, ini dimungkinkan tanpa menginstal paket tambahan:

python -c "from lxml.etree import parse; from sys import stdin; print '\n'.join(parse(stdin).xpath('//element/@attribute'))"
klak
sumber
1
Bagaimana cara mengirimkan nama file?
Ramakrishnan Kannan
4
Ini berhasil stdin. Itu menghilangkan kebutuhan untuk memasukkan open()dan close()dalam one-liner yang sudah cukup panjang. Untuk mem-parsing file, jalankan saja python -c "from lxml.etree import parse; from sys import stdin; print '\n'.join(parse(stdin).xpath('//element/@attribute'))" < my_file.xmldan biarkan shell Anda menangani pencarian file, membuka dan menutup.
clacke
10

Dalam pencarian saya untuk meminta file maven pom.xml saya berlari di pertanyaan ini. Namun saya memiliki batasan berikut:

  • harus menjalankan lintas platform.
  • harus ada di semua distribusi linux utama tanpa pemasangan modul tambahan
  • harus menangani file xml yang kompleks seperti file maven pom.xml
  • sintaksis sederhana

Saya telah mencoba banyak hal di atas tanpa hasil:

  • python lxml.etree bukan bagian dari distribusi python standar
  • xml.etree adalah tetapi tidak menangani file maven pom.xml yang rumit dengan baik, belum menggali cukup dalam
  • python xml.etree tidak menangani file maven pom.xml karena alasan yang tidak diketahui
  • xmllint juga tidak berfungsi, core dumps sering di ubuntu 12.04 "xmllint: using libxml versi 20708"

Solusi yang saya temui adalah stabil, pendek dan bekerja pada banyak platform dan yang matang adalah rexml lib builtin di ruby:

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

Apa yang mengilhami saya untuk menemukan yang ini adalah artikel-artikel berikut:

Mike
sumber
1
Itu bahkan kriteria yang lebih sempit daripada pertanyaan, jadi itu pasti cocok sebagai jawaban. Saya yakin banyak orang yang menghadapi situasi Anda akan terbantu dengan riset Anda. Saya menjaga xmlstarletsebagai jawaban yang diterima, karena cocok dengan kriteria saya yang lebih luas dan sangat rapi . Tapi saya mungkin akan menggunakan solusi Anda dari waktu ke waktu.
clacke
2
Saya akan menambahkan itu untuk menghindari tanda kutip di sekitar hasil , gunakan putsbukannya pdalam perintah Ruby.
tooomg
10

Saxon akan melakukan ini tidak hanya untuk XPath 2.0, tetapi juga untuk XQuery 1.0 dan (dalam versi komersial) 3.0. Itu tidak datang sebagai paket Linux, tetapi sebagai file jar. Sintaks (yang Anda dapat dengan mudah membungkus skrip sederhana) adalah

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

UPDATE 2020

Saxon 10.0 termasuk alat Gizmo, yang dapat digunakan secara interaktif atau dalam batch dari baris perintah. Sebagai contoh

java net.sf.saxon.Gizmo -s:source.xml
/>show //element/@attribute
/>quit
Michael Kay
sumber
SaxonB ada di Ubuntu, paket libsaxonb-java, tetapi jika saya menjalankan saxonb-xquery -qs://element/@attribute -s:filename.xmlsaya mendapatkan SENR0001: Cannot serialize a free-standing attribute node, masalah yang sama dengan misalnya xml_grep.
clacke
3
Jika Anda ingin melihat detail lengkap dari simpul atribut yang dipilih oleh kueri ini, gunakan opsi -wrap pada baris perintah. Jika Anda hanya ingin nilai string atribut, tambahkan / string () ke kueri.
Michael Kay
Terima kasih. Menambahkan / string () semakin dekat. Tetapi output header XML dan menempatkan semua hasil pada satu baris, jadi masih tidak ada cerutu.
clacke
2
Jika Anda tidak menginginkan header XML, tambahkan opsi! Method = teks.
Michael Kay
Untuk menggunakan namespace, tambahkan -qsseperti ini:'-qs:declare namespace mets="http://www.loc.gov/METS/";/mets:mets/mets:dmdSec'
igo
5

Anda mungkin juga tertarik dengan xsh . Ini fitur mode interaktif di mana Anda dapat melakukan apa pun yang Anda suka dengan dokumen:

open 1.xml ;
ls //element/@id ;
for //p[@class="first"] echo text() ;
choroba
sumber
Tampaknya tidak tersedia sebagai paket, setidaknya tidak di Ubuntu.
clacke
1
@clacke: Tidak, tapi bisa diinstal dari CPAN oleh cpan XML::XSH2.
choroba
@ choroba, saya sudah mencobanya di OS X, tetapi gagal diinstal, dengan semacam kesalahan makefile.
cnst
@ cnst: Apakah Anda sudah menginstal XML :: LibXML?
choroba
@ choroba, saya tidak tahu; tapi maksud saya adalah, cpan XML::XSH2gagal menginstal apa pun.
cnst
5

jawaban clacke bagus tetapi saya pikir hanya berfungsi jika sumber Anda adalah XML yang dibentuk dengan baik, bukan HTML normal.

Jadi untuk melakukan hal yang sama untuk konten Web normal — dokumen HTML yang belum tentu XML terbentuk dengan baik:

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

Dan untuk menggunakan html5lib (untuk memastikan Anda mendapatkan perilaku parsing yang sama dengan browser Web — karena seperti parser browser, html5lib sesuai dengan persyaratan parsing dalam spesifikasi 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'))
sontonbarker
sumber
Ya, saya jatuh cinta pada asumsi saya sendiri dalam pertanyaan, bahwa XPath menyiratkan XML. Jawaban ini adalah pelengkap yang baik untuk yang lain di sini, dan terima kasih telah memberi tahu saya tentang html5lib!
clacke
3

Mirip dengan jawaban Mike dan clacke, ini adalah python one-liner (menggunakan python> = 2.5) untuk mendapatkan versi build dari file pom.xml yang mengelak dari fakta bahwa file pom.xml biasanya tidak memiliki dtd atau namespace default, jadi jangan muncul dengan baik untuk 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)"

Diuji pada Mac dan Linux, dan tidak memerlukan paket tambahan untuk diinstal.

pdr
sumber
2
Saya menggunakan ini hari ini! Kami membangun server memiliki tidak lxmlatau xmllint, atau bahkan Ruby. Dalam semangat format dalam jawaban saya sendiri , saya menulisnya sebagai python3 -c "from xml.etree.ElementTree import parse; from sys import stdin; print(parse(stdin).find('.//element[subelement=\"value\"]/othersubelement').text)" <<< "$variable_containing_xml"bash. .getroot()sepertinya tidak perlu.
clacke
2

Selain XML :: XSH dan XML :: XSH2 ada beberapa greputilitas seperti menyedot sebagai App::xml_grep2dan XML::Twig(yang termasuk xml_grepdaripada xml_grep2). Ini bisa sangat berguna ketika bekerja pada file XML besar atau banyak untuk oneliners atau Makefiletarget cepat. XML::Twigterutama baik untuk bekerja dengan untuk perlpendekatan scripting ketika Anda ingin sedikit aa lebih pengolahan dari Anda $SHELLdan xmllint xstlprocditawarkan.

Skema penomoran dalam nama aplikasi menunjukkan bahwa versi "2" adalah versi yang lebih baru / lebih baru dari alat yang sama yang mungkin memerlukan versi modul lain yang lebih baru (atau dari perldirinya sendiri).

G. Cito
sumber
xml_grep2 -t //element@attribute filename.xmlberfungsi dan melakukan apa yang saya harapkan ( xml_grep --root //element@attribute --text_only filename.xmlmasih belum, mengembalikan kesalahan "ekspresi tidak dikenal"). Bagus!
clacke
Bagaimana dengan xml_grep --pretty_print --root '//element[@attribute]' --text_only filename.xml? Tidak yakin apa yang terjadi di sana atau apa yang dikatakan XPath []dalam kasus ini, tetapi mengelilingi @attributetanda kurung siku bekerja untuk xml_grepdan xml_grep2.
G. Cito
Maksudku //element/@attribute, tidak //element@attribute. Tampaknya tidak dapat mengeditnya, tetapi membiarkannya di sana daripada menghapus + ganti agar tidak membingungkan riwayat diskusi ini.
clacke
//element[@attribute]memilih elemen tipe elementyang memiliki atribut attribute. Saya tidak ingin elemen, hanya atributnya. <element attribute='foo'/>harus memberi saya foo, bukan yang penuh <element attribute='foo'/>.
clacke
... dan --text_onlydalam konteks itu memberi saya string kosong dalam kasus elemen seperti <element attribute='foo'/>tanpa simpul teks di dalamnya.
clacke
2

Perlu disebutkan bahwa nokogiri sendiri dikirimkan dengan alat baris perintah, yang harus diinstal bersama gem install nokogiri.

Anda mungkin menemukan posting blog ini bermanfaat .

Geoff Nixon
sumber
2

Saya telah mencoba beberapa utilitas baris perintah XPath dan ketika saya menyadari saya menghabiskan terlalu banyak waktu untuk mencari dan mencari tahu bagaimana mereka bekerja, jadi saya menulis parser XPath yang paling sederhana dengan Python yang melakukan apa yang saya butuhkan.

Script di bawah ini menunjukkan nilai string jika ekspresi XPath mengevaluasi ke string, atau menunjukkan seluruh subnode XML jika hasilnya adalah simpul:

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

Menggunakan lxml- parser XML cepat yang ditulis dalam C yang tidak termasuk dalam pustaka python standar. Instal dengan pip install lxml. Di Linux / OSX mungkin perlu diawali dengan sudo.

Pemakaian:

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

lxml juga dapat menerima URL sebagai input:

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

Ekstrak atribut url di bawah simpul enklosur yaitu <enclosure url="http:...""..>):

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

Xpath di Google Chrome

Sebagai catatan samping yang tidak terkait: Jika kebetulan Anda ingin menjalankan ekspresi XPath terhadap markup halaman web maka Anda dapat melakukannya langsung dari Chrome devtools: klik kanan halaman di Chrome> pilih Inspect, lalu di DevTools konsol tempelkan ekspresi XPath Anda sebagai $x("//spam/eggs").

Dapatkan semua penulis di halaman ini:

$x("//*[@class='user-details']/a/text()")
ccpizza
sumber
Bukan satu kalimat, dan lxmlsudah disebutkan dalam dua jawaban lain bertahun-tahun sebelum Anda.
clacke
2

Berikut ini satu use case xmlstarlet untuk mengekstrak data dari elemen bersarang elem1, elem2 ke satu baris teks dari tipe XML ini (juga menunjukkan cara menangani ruang nama):

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

Outputnya adalah

0.586 10.586 cue-in outro

Dalam cuplikan ini, -m cocok dengan nilai atribut bersarang elem2, -v output (dengan ekspresi dan pengalamatan relatif), -o teks literal, -n menambahkan baris baru:

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

Jika diperlukan lebih banyak atribut dari elem1, seseorang dapat melakukannya seperti ini (juga memperlihatkan fungsi 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

Perhatikan komplikasi (IMO yang tidak perlu) dengan ruang nama (ns, dideklarasikan dengan -N), yang membuat saya hampir menyerah pada xpath dan xmlstarlet, dan menulis konverter ad-hoc cepat.

diemo
sumber
xmlstarlet bagus, tetapi jawaban peringkat utama dan diterima sudah menyebutkannya. Informasi tentang cara menangani ruang nama mungkin relevan sebagai komentar, jika sama sekali. Siapa pun yang mengalami masalah dengan namespace dan xmlstarlet dapat menemukan diskusi yang
clacke
2
Tentu, @clacke, xmlstarlet telah disebutkan beberapa kali, tetapi juga sulit untuk dipahami, dan tidak terdokumentasi. Saya menebak sekitar satu jam bagaimana cara mendapatkan informasi dari elemen bersarang. Saya berharap saya punya contoh itu, itu sebabnya saya mempostingnya di sini untuk menghindari orang lain yang kehilangan waktu (dan contohnya terlalu panjang untuk komentar).
diemo
2

Skrip Python saya xgrep.py melakukan persis ini. Untuk mencari semua atribut attributeelemen elementdalam file filename.xml ..., Anda akan menjalankannya sebagai berikut:

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

Ada berbagai sakelar untuk mengendalikan keluaran, seperti -cuntuk menghitung kecocokan, -iuntuk indentasi bagian yang cocok, dan -luntuk keluaran hanya nama file.

Skrip tidak tersedia sebagai paket Debian atau Ubuntu, tetapi semua dependensinya adalah.

Andreas Nolda
sumber
Dan Anda hosting di sourcehut! Bagus!
clacke
1

Karena proyek ini tampaknya cukup baru, periksa https://github.com/jeffbr13/xq , tampaknya menjadi pembungkus lxml, tetapi hanya itu yang Anda butuhkan (dan kirimkan solusi ad hoc menggunakan lxml dalam jawaban lain juga)

mgrandi
sumber
1

Saya tidak senang dengan Python one-liners untuk permintaan HTML XPath, jadi saya menulis sendiri. Asumsikan bahwa Anda menginstal python-lxmlpaket atau berlari 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 }

Setelah memilikinya, Anda dapat menggunakannya seperti dalam contoh ini:

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

Instal basis data BaseX , lalu gunakan "mode baris perintah mandiri" seperti ini:

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

atau

basex -i filename.xml //element@attribute

Bahasa query sebenarnya XQuery (3.0), bukan XPath, tetapi karena XQuery adalah superset dari XPath, Anda dapat menggunakan permintaan XPath tanpa pernah memperhatikan.

igneus
sumber