Làm cách nào để thực thi XPath one-liners từ shell?


192

Có một gói ngoài đó, cho Ubuntu và / hoặc CentOS, có một công cụ dòng lệnh có thể thực thi một XPath như một lớp lót foo //element@attribute filename.xmlhoặc foo //element@attribute < filename.xmltrả về dòng kết quả theo từng dòng không?

Tôi đang tìm kiếm thứ gì đó cho phép tôi chỉ apt-get install foohoặc yum install foosau đó chỉ hoạt động ngoài luồng, không có trình bao bọc hoặc điều chỉnh khác cần thiết.

Dưới đây là một số ví dụ về những điều đến gần:

Nokogiri. Nếu tôi viết trình bao bọc này, tôi có thể gọi trình bao bọc theo cách được mô tả ở trên:

#!/usr/bin/ruby

require 'nokogiri'

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

XML :: XPath. Sẽ làm việc với trình bao bọc này:

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

xpathtừ XML :: XPath trả lại quá nhiều tiếng ồn -- NODE --attribute = "value".

xml_grep từ XML :: Twig không thể xử lý các biểu thức không trả về các phần tử, do đó không thể được sử dụng để trích xuất các giá trị thuộc tính mà không cần xử lý thêm.

BIÊN TẬP:

echo cat //element/@attribute | xmllint --shell filename.xmltrả về tiếng ồn tương tự như xpath.

xmllint --xpath //element/@attribute filename.xmltrả lại attribute = "value".

xmllint --xpath 'string(//element/@attribute)' filename.xml trả về những gì tôi muốn, nhưng chỉ cho trận đấu đầu tiên.

Đối với một giải pháp khác gần như thỏa mãn câu hỏi, đây là một XSLT có thể được sử dụng để đánh giá các biểu thức XPath tùy ý (yêu cầu dyn: đánh giá hỗ trợ trong bộ xử lý 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>

Chạy với xsltproc --stringparam pattern //element/@attribute --stringparam value . arbitrary-xpath.xslt filename.xml.


1 cho câu hỏi hay và cho não về việc tìm kiếm một đơn giản và cách đáng tin cậy để in nhiều kết quả mỗi ngày một xuống dòng
Gilles Quenot

1
Lưu ý rằng "tiếng ồn" từ xpathtrên STDERR chứ không phải STDOUT.
miken32

@ miken32 Không. Tôi chỉ muốn giá trị cho đầu ra. hastebin.com/ekarexumeg.bash
clacke

Câu trả lời:


271

Bạn nên thử những công cụ sau:

  • xmlstarlet : có thể chỉnh sửa, chọn, chuyển đổi ... Không được cài đặt theo mặc định, xpath1
  • xmllint: thường được cài đặt theo mặc định với libxml2-utils, xpath1 (kiểm tra trình bao bọc của tôi để --xpathchuyển sang các bản phát hành rất cũ và đầu ra được phân tách bằng dòng mới (v <2.9.9)
  • xpath: được cài đặt qua mô-đun của perl XML::XPath, xpath1
  • xml_grep: được cài đặt qua mô-đun của perl XML::Twig, xpath1 (sử dụng xpath giới hạn)
  • xidel: xpath3
  • saxon-lint : dự án của riêng tôi, trình bao bọc thư viện Java Saxon-HE của @Michael Kay, xpath3

xmllintđi kèm libxml2-utils(có thể được sử dụng làm vỏ tương tác với công --shelltắc)

xmlstarletxmlstarlet.

xpath đi kèm với mô-đun của perl XML::Xpath

xml_grep đi kèm với mô-đun của perl XML::Twig

xidelxidel

saxon-lintsử dụng SaxonHE 9.6 , XPath 3.x (tương thích retro)

Ví dụ :

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
Thông minh! xmlstarlet sel -T -t -m '//element/@attribute' -v '.' -n filename.xmllàm chính xác những gì tôi muốn!
clacke 17/03/13

2
Lưu ý: xmlstarlet đã bị đồn là bị bỏ rơi, nhưng hiện đang được phát triển tích cực trở lại.
clacke

6
Lưu ý: Một số phiên bản cũ hơn xmllintkhông hỗ trợ đối số dòng lệnh --xpath, nhưng hầu hết dường như hỗ trợ --shell. Đầu ra bẩn hơn một chút, nhưng vẫn hữu ích trong một liên kết.
kevinarpe

Tôi dường như vẫn gặp khó khăn khi truy vấn nội dung nút, không phải là một thuộc tính. Bất cứ ai có thể cung cấp một ví dụ cho điều đó? Vì một số lý do, tôi vẫn thấy xmlstarlet khó tìm ra và hiểu đúng giữa khớp, giá trị, gốc để chỉ xem cấu trúc tài liệu, v.v. Ngay cả với sel -t -m ... -v ...ví dụ đầu tiên từ trang này: arstechnica.com/inif-t Technology / 2005 / 11 / linux-20051115/2 , khớp với tất cả trừ nút cuối cùng và lưu nút đó cho biểu thức giá trị như trường hợp sử dụng của tôi, tôi dường như vẫn không thể có được nó, tôi chỉ nhận được đầu ra trống ..
Pysis

một phiên bản hay của xpath - Tôi vừa gặp phải giới hạn của xmllint xuất sắc này
JonnyRaa

20

Bạn cũng có thể thử Xidel của tôi . Nó không có trong một gói trong kho lưu trữ, nhưng bạn chỉ có thể tải xuống từ trang web (nó không có phụ thuộc).

Nó có cú pháp đơn giản cho nhiệm vụ này:

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

Và nó là một trong những công cụ hiếm hoi hỗ trợ XPath 2.


2
Xidel trông khá tuyệt, mặc dù có lẽ bạn nên đề cập rằng bạn cũng là tác giả của công cụ này mà bạn đề xuất.
Thất

1
Saxon và saxon-lint sử dụng xpath3;)
Gilles Quenot

Xidel (0..8.win32.zip) hiển thị là có phần mềm độc hại trên Virustotal. Vì vậy, hãy thử với rủi ro của riêng bạn virustotal.com/#/file/ Lần
JGFMK

tuyệt vời - Tôi sẽ thêm xidel vào hộp công cụ cờ lê cá nhân của mình
maoizm

15

Một gói rất có khả năng được cài đặt trên một hệ thống là python-lxml. Nếu vậy, điều này là có thể mà không cần cài đặt bất kỳ gói bổ sung:

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

1
Làm thế nào để vượt qua tên tập tin?
Ramakrishnan Kannan

4
Điều này hoạt động trên stdin. Điều đó loại bỏ sự cần thiết phải bao gồm open()close()trong một lớp lót khá dài. Để phân tích một tệp, chỉ cần chạy python -c "from lxml.etree import parse; from sys import stdin; print '\n'.join(parse(stdin).xpath('//element/@attribute'))" < my_file.xmlvà để trình bao của bạn xử lý việc tra cứu, mở và đóng tệp.
clacke

10

Trong quá trình tìm kiếm để truy vấn các tệp maven pom.xml, tôi đã chạy qua câu hỏi này. Tuy nhiên tôi đã có những hạn chế sau:

  • phải chạy đa nền tảng.
  • phải tồn tại trên tất cả các bản phân phối linux chính mà không cần cài đặt mô-đun bổ sung
  • phải xử lý các tệp xml phức tạp, chẳng hạn như các tệp maven pom.xml
  • cú pháp đơn giản

Tôi đã thử nhiều cách trên mà không thành công:

  • python lxml.etree không phải là một phần của phân phối python tiêu chuẩn
  • xml.etree là nhưng không xử lý tốt các tệp maven pom.xml phức tạp, chưa đào sâu đủ
  • python xml.etree không xử lý các tệp maven pom.xml mà không rõ lý do
  • xmllint cũng không hoạt động, các kết xuất lõi thường trên Ubuntu 12.04 "xmllint: sử dụng libxml phiên bản 20708"

Giải pháp mà tôi đã tìm thấy là ổn định, ngắn gọn và hoạt động trên nhiều nền tảng và đó là sự trưởng thành là rexml lib dựng sẵn trong ruby:

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

Điều truyền cảm hứng cho tôi để tìm thấy điều này là các bài viết sau:


1
Điều đó thậm chí còn hẹp hơn tiêu chí so với câu hỏi, vì vậy nó chắc chắn phù hợp như một câu trả lời. Tôi chắc rằng nhiều người gặp phải tình huống của bạn sẽ được nghiên cứu của bạn giúp đỡ. Tôi đang giữ xmlstarletcâu trả lời được chấp nhận, bởi vì nó phù hợp với tiêu chí rộng hơn của tôi và nó thực sự gọn gàng . Nhưng tôi có thể sẽ sử dụng cho giải pháp của bạn theo thời gian.
clacke

2
Tôi sẽ thêm rằng để tránh các trích dẫn xung quanh kết quả , sử dụng putsthay vì ptrong lệnh Ruby.
TomG

10

Saxon sẽ làm điều này không chỉ cho XPath 2.0, mà còn cho XQuery 1.0 và (trong phiên bản thương mại) 3.0. Nó không phải là một gói Linux, mà là một tệp jar. Cú pháp (mà bạn có thể dễ dàng gói trong một tập lệnh đơn giản) là

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

CẬP NHẬT 2020

Saxon 10.0 bao gồm công cụ Gizmo, có thể được sử dụng tương tác hoặc theo đợt từ dòng lệnh. Ví dụ

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

SaxonB có trong Ubuntu, gói libsaxonb-java, nhưng nếu tôi chạy saxonb-xquery -qs://element/@attribute -s:filename.xmltôi nhận được SENR0001: Cannot serialize a free-standing attribute node, vấn đề tương tự như với ví dụ xml_grep.
clacke

3
Nếu bạn muốn xem chi tiết đầy đủ về nút thuộc tính được chọn bởi truy vấn này, hãy sử dụng tùy chọn -wrap trên dòng lệnh. Nếu bạn chỉ muốn giá trị chuỗi của thuộc tính, hãy thêm / string () vào truy vấn.
Michael Kay

Cảm ơn. Thêm / chuỗi () được gần hơn. Nhưng nó xuất ra một tiêu đề XML và đặt tất cả các kết quả trên một hàng, do đó vẫn không có điếu xì gà.
clacke

2
Nếu bạn không muốn một tiêu đề XML, hãy thêm tùy chọn! Method = text.
Michael Kay

Để sử dụng không gian tên, hãy thêm nó vào -qsnhư thế này:'-qs:declare namespace mets="http://www.loc.gov/METS/";/mets:mets/mets:dmdSec'
igo

5

Bạn cũng có thể quan tâm đến xsh . Nó có chế độ tương tác nơi bạn có thể làm bất cứ điều gì bạn muốn với tài liệu:

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

Nó dường như không có sẵn như là một gói, ít nhất là không có trong Ubuntu.
clacke 17/03/13

1
@clacke: Không, nhưng nó có thể được cài đặt từ CPAN cpan XML::XSH2.
choroba

@choroba, tôi đã thử điều đó trên OS X, nhưng không cài đặt được, với một số lỗi makefile.
cnst

@cnst: Bạn đã cài đặt XML :: LibXML chưa?
choroba

@choroba, tôi không biết; Nhưng quan điểm của tôi là, cpan XML::XSH2không cài đặt được gì.
cnst

5

Câu trả lời của clacke rất hay nhưng tôi nghĩ chỉ hoạt động nếu nguồn của bạn là XML được định dạng tốt chứ không phải HTML thông thường.

Vì vậy, để làm điều tương tự đối với nội dung Web thông thường, các tài liệu HTML HTML không nhất thiết phải được định dạng tốt:

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

Và thay vào đó, hãy sử dụng html5lib (để đảm bảo bạn có hành vi phân tích cú pháp tương tự như trình duyệt Web, vì giống như trình phân tích cú pháp trình duyệt, html5lib tuân thủ các yêu cầu phân tích cú pháp trong thông số 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'))

Vâng, tôi đã rơi vào giả định của riêng mình trong câu hỏi, rằng XPath ngụ ý XML. Câu trả lời này là một bổ sung tốt cho những người khác ở đây, và cảm ơn vì đã cho tôi biết về html5lib!
clacke

3

Tương tự như câu trả lời của Mike và clacke, đây là python one-liner (sử dụng python> = 2.5) để lấy phiên bản xây dựng từ tệp pom.xml xung quanh thực tế là các tệp pom.xml thường không có dtd hoặc không gian tên mặc định, vì vậy không xuất hiện đúng với 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)"

Đã thử nghiệm trên Mac và Linux và không yêu cầu bất kỳ gói bổ sung nào được cài đặt.


2
Tôi đã sử dụng ngày hôm nay! Xây dựng các máy chủ của chúng tôi đã không phải lxmlvà cũng không xmllint, hoặc thậm chí Ruby. Theo tinh thần của định dạng trong câu trả lời của riêng tôi , tôi đã viết nó như python3 -c "from xml.etree.ElementTree import parse; from sys import stdin; print(parse(stdin).find('.//element[subelement=\"value\"]/othersubelement').text)" <<< "$variable_containing_xml"trong bash. .getroot()dường như không cần thiết
clacke

2

Ngoài XML :: XSHXML :: XSH2 , còn có một số greptiện ích giống như hút App::xml_grep2XML::Twig(bao gồm xml_grepchứ không phải xml_grep2). Chúng có thể khá hữu ích khi làm việc trên một hoặc nhiều tệp XML để truy cập nhanh hoặc Makefilecác mục tiêu. XML::Twigđặc biệt thoải mái để làm việc với một perlcách tiếp cận kịch bản khi bạn muốn nhiều aa chút chế biến hơn của bạn $SHELLxmllint xstlproccung cấp.

Lược đồ đánh số trong các tên ứng dụng chỉ ra rằng các phiên bản "2" là phiên bản mới hơn / mới hơn của cùng một công cụ có thể yêu cầu các phiên bản mới hơn của các mô-đun khác (hoặc của perlchính nó).


xml_grep2 -t //element@attribute filename.xmlhoạt động và thực hiện những gì tôi mong đợi ( xml_grep --root //element@attribute --text_only filename.xmlvẫn không, trả về lỗi "biểu thức không được nhận dạng"). Tuyệt quá!
clacke

Thế còn xml_grep --pretty_print --root '//element[@attribute]' --text_only filename.xml? Không chắc chắn những gì đang xảy ra ở đó hoặc XPath nói gì []trong trường hợp này, nhưng xung quanh một @attributedấu ngoặc vuông hoạt động cho xml_grepxml_grep2.
G. Cito

Ý tôi là //element/@attribute, không phải //element@attribute. Không thể chỉnh sửa nó rõ ràng, nhưng để nó ở đó thay vì xóa + thay thế để không nhầm lẫn lịch sử của cuộc thảo luận này.
clacke

//element[@attribute]chọn các phần tử của loại elementcó thuộc tính attribute. Tôi không muốn phần tử, chỉ có thuộc tính. <element attribute='foo'/>Nên cho tôi foo, không đầy đủ <element attribute='foo'/>.
clacke

... và --text_onlytrong bối cảnh đó cung cấp cho tôi chuỗi trống trong trường hợp một phần tử như <element attribute='foo'/>không có nút văn bản bên trong.
clacke


2

Tôi đã thử một vài tiện ích XPath dòng lệnh và khi tôi nhận ra mình đang dành quá nhiều thời gian để tìm hiểu và tìm ra cách chúng hoạt động, vì vậy tôi đã viết trình phân tích cú pháp XPath đơn giản nhất có thể trong Python, thứ tôi cần.

Kịch bản bên dưới hiển thị giá trị chuỗi nếu biểu thức XPath ước tính thành một chuỗi hoặc hiển thị toàn bộ mã con XML nếu kết quả là một nút:

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

Nó sử dụng lxml- một trình phân tích cú pháp XML nhanh được viết bằng C không có trong thư viện python tiêu chuẩn. Cài đặt nó với pip install lxml. Trên Linux / OSX có thể cần tiền tố với sudo.

Sử dụng:

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

lxml cũng có thể chấp nhận một URL làm đầu vào:

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

Trích xuất thuộc tính url dưới một nút bao vây, tức là <enclosure url="http:...""..>):

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

Xpath trong Google Chrome

Là một lưu ý phụ không liên quan: Nếu tình cờ bạn muốn chạy biểu thức XPath chống lại việc đánh dấu trang web thì bạn có thể thực hiện trực tiếp từ devtools của Chrome: nhấp chuột phải vào trang trong Chrome> chọn Kiểm tra, sau đó vào DevTools Bảng điều khiển dán biểu thức XPath của bạn dưới dạng $x("//spam/eggs") .

Nhận tất cả các tác giả trên trang này:

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

Không phải là một lót, và lxmlđã được đề cập trong hai câu trả lời khác trước năm của bạn.
clacke

2

Đây là trường hợp sử dụng một xmlstarlet để trích xuất dữ liệu từ các phần tử lồng nhau elem1, elem2 sang một dòng văn bản từ loại XML này (cũng cho thấy cách xử lý các không gian tên):

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

Đầu ra sẽ là

0.586 10.586 cue-in outro

Trong đoạn mã này, -m khớp với các giá trị thuộc tính đầu ra elem2, -v lồng nhau (với các biểu thức và địa chỉ tương đối), -o văn bản bằng chữ, -n thêm một dòng mới:

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

Nếu cần thêm thuộc tính từ elem1, người ta có thể làm như thế này (cũng hiển thị hàm 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

Lưu ý sự phức tạp (không cần thiết của IMO) với các không gian tên (ns, được khai báo bằng -N), khiến tôi gần như từ bỏ xpath và xmlstarlet và viết một trình chuyển đổi ad-hoc nhanh chóng.


xmlstarlet là tuyệt vời, nhưng câu trả lời xếp hạng chính và được chấp nhận đã đề cập đến nó. Thông tin về cách xử lý các không gian tên có thể có liên quan như một nhận xét, nếu có. Bất cứ ai gặp vấn đề với không gian tên và xmlstarlet đều có thể tìm thấy một cuộc thảo luận
clacke

2
Chắc chắn, @clacke, xmlstarlet đã được đề cập nhiều lần, nhưng cũng rất khó để nắm bắt và không được đánh giá cao. Tôi đã đoán khoảng một giờ làm thế nào để lấy thông tin ra khỏi các yếu tố lồng nhau. Tôi ước tôi đã có ví dụ đó, đó là lý do tại sao tôi đăng nó ở đây để tránh những người khác mất thời gian (và ví dụ này quá dài cho một bình luận).
diemo

2

Tập lệnh Python của tôi xgrep.py thực hiện chính xác điều này. Để tìm kiếm tất cả các thuộc tính attributecủa các thành phần elementtrong tệp filename.xml ..., bạn sẽ chạy nó như sau:

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

Có nhiều công tắc khác nhau để kiểm soát đầu ra, chẳng hạn như -cđể đếm các trận đấu, -iđể thụt vào các phần khớp và-l chỉ xuất ra tên tệp.

Kịch bản không có sẵn dưới dạng gói Debian hoặc Ubuntu, nhưng tất cả các phụ thuộc của nó là.


Và bạn đang lưu trữ trên sourcehut! Đẹp!
clacke

1

Vì dự án này có vẻ khá mới, hãy xem https://github.com/jeffbr13/xq , dường như là một trình bao bọc xung quanh lxml, nhưng đó là tất cả những gì bạn thực sự cần (và cũng đăng các giải pháp ad hoc sử dụng lxml trong các câu trả lời khác)


1

Tôi không hài lòng với Python một lớp cho các truy vấn HTML XPath, vì vậy tôi đã tự viết. Giả sử rằng bạn đã cài đặt python-lxmlgói hoặc chạy 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 }

Khi bạn đã có nó, bạn có thể sử dụng nó như trong ví dụ này:

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

0

Cài đặt cơ sở dữ liệu BaseX , sau đó sử dụng "chế độ dòng lệnh độc lập" như thế này:

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

hoặc là

basex -i filename.xml //element@attribute

Ngôn ngữ truy vấn thực sự là XQuery (3.0), không phải XPath, nhưng vì XQuery là siêu bộ của XPath, bạn có thể sử dụng các truy vấn XPath mà không bao giờ nhận thấy.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.