Як виконати однорядні XPath з оболонки?


192

Чи є там пакет для Ubuntu та / або CentOS, який має інструмент командного рядка, який може виконати однорядний XPath типу foo //element@attribute filename.xmlабо foo //element@attribute < filename.xmlповернути результати за рядком?

Я шукав що - то , що дозволить мені просто apt-get install fooабо , yum install fooа потім просто працює поза коробки, без обгортки або інший адаптації необхідно.

Ось кілька прикладів речей, які наближаються:

Нокогірі. Якщо я напишу цю обгортку, я можу викликати обгортку описаним вище способом:

#!/usr/bin/ruby

require 'nokogiri'

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

XML :: XPath. Працює з цією обгорткою:

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

xpathвід XML :: XPath повертає занадто багато шуму -- NODE --та attribute = "value".

xml_grep від XML :: Twig не може обробляти вирази, які не повертають елементи, тому їх не можна використовувати для отримання значень атрибутів без подальшої обробки.

Редагувати:

echo cat //element/@attribute | xmllint --shell filename.xmlповертає шум, подібний до xpath.

xmllint --xpath //element/@attribute filename.xmlповертає attribute = "value".

xmllint --xpath 'string(//element/@attribute)' filename.xml повертає те, що я хочу, але лише за перший матч.

Ще одне рішення, що майже задовольняє питання, ось XSLT, який може бути використаний для оцінки довільних виразів XPath (потрібен dyn: оцінка підтримки в процесорі 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>

Біжи з xsltproc --stringparam pattern //element/@attribute --stringparam value . arbitrary-xpath.xslt filename.xml.


+1 за гарне запитання та мозковий штурм щодо пошуку простого та надійного способу друку декількох результатів на
новому рядку

1
Зауважте, що "шум" від xpathє на STDERR, а не на STDOUT.
miken32

@ miken32 Ні. Я хотів лише значення для виводу. hastebin.com/ekarexumeg.bash
clacke

Відповіді:


271

Спробуйте скористатися цими інструментами:

  • xmlstarlet : може редагувати, вибирати, перетворювати ... Не встановлено за замовчуванням, xpath1
  • xmllint: часто встановлюється за замовчуванням з libxml2-utils, xpath1 (перевірте, чи моя обгортка має --xpathперемикання на дуже старі випуски та обмеження вихідних рядків (v <2.9.9)
  • xpath: встановлено через модуль perl XML::XPath, xpath1
  • xml_grep: встановлено через модуль perl XML::Twig, xpath1 (обмежене використання xpath)
  • xidel: xpath3
  • saxon-lint : мій власний проект, обгортка над бібліотекою Java Saxon-HE Java @Michael Kay, xpath3

xmllintпоставляється з libxml2-utils(може використовуватися як інтерактивна оболонка з --shellперемикачем)

xmlstarletє xmlstarlet.

xpath поставляється з модулем perl XML::Xpath

xml_grep поставляється з модулем perl XML::Twig

xidel є xidel

saxon-lintвикористання SaxonHE 9.6 , XPath 3.x (+ ретро сумісність)

Наприклад:

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
Відмінно! xmlstarlet sel -T -t -m '//element/@attribute' -v '.' -n filename.xmlробить саме те, що я хочу!
clacke

2
Примітка: за чутками, xmlstarlet відмовився, але зараз знову в активному розвитку.
клак

6
Примітка. Деякі старі версії xmllintне підтримують аргумент командного рядка --xpath, але більшість, здається, підтримують --shell. Трохи брудніший вихід, але все-таки корисний у зв’язуванні.
кевінарпе

У мене все ще виникають проблеми із запитом вмісту вузла, а не атрибутом. Хтось може навести приклад для цього? З якихось причин мені все ще здається, що xmlstarlet важко розібратися і отримати потрібне значення між співвідношенням, значенням, root, щоб просто переглянути структуру документа тощо. Навіть із першого sel -t -m ... -v ...прикладу на цій сторінці: arstechnica.com/information-technology/2005 / 11 / linux-20051115/2 , збігаючи всі, крім останнього вузла, і зберігаючи це для вираження значення, як у моєму випадку використання, я все ще не можу його отримати, я просто отримую порожній вихід ..
Pysis

приємна версія версії xpath - я б просто зіткнувся з цим обмеженням інакше відмінного xmllint
JonnyRaa

20

Ви також можете спробувати мій Xidel . Він не знаходиться в пакеті у сховищі, але ви можете просто завантажити його з веб-сторінки (він не має залежностей).

Для цього завдання є простий синтаксис:

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

І це один з рідкісних цих інструментів, який підтримує XPath 2.


2
Xidel виглядає досить круто, хоча, напевно, слід згадати, що ви також автор цього інструменту, який ви рекомендуєте.
FrustratedWithFormsDesigner

1
Саксон і саксонський лент використовують xpath3;)
Жиль-Кінот

Xidel (0..8.win32.zip) виявляється як зловмисне програмне забезпечення у Virustotal. Тому спробуйте на свій страх і ризик virustotal.com/#/file/…
JGFMK

чудово - я збираюся додати Xidel до моєї особистої скриньки інструментів гайкового ключа
maoizm

15

Один пакет, який, швидше за все, буде встановлений у системі, вже є python-lxml. Якщо так, це можливо без встановлення додаткового пакету:

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

1
Як передати ім'я файлу?
Рамакришнан Каннан

4
Це працює далі stdin. Це позбавляє від необхідності включати open()і close()вже досить тривалий одноколісний. Щоб розібрати файл, просто запустіть python -c "from lxml.etree import parse; from sys import stdin; print '\n'.join(parse(stdin).xpath('//element/@attribute'))" < my_file.xmlі дозвольте своїй оболонці обробляти пошук, відкриття та закриття файлу.
clacke

10

У моєму пошуку запитів файлів maven pom.xml я зіткнувся з цим питанням. Однак у мене були такі обмеження:

  • повинен працювати на крос-платформі.
  • повинні існувати у всіх основних дистрибутивах Linux без додаткової установки модуля
  • повинен обробляти складні xml-файли, такі як файли maven pom.xml
  • простий синтаксис

Я багато разів перераховував вищезазначене:

  • python lxml.etree не є частиною стандартного розподілу python
  • xml.etree є, але не обробляє складні файли Maven pom.xml добре, не викопав достатньо глибоко
  • python xml.etree не обробляє файли Maven pom.xml з незрозумілої причини
  • xmllint також не працює, ядро ​​скидає часто на ubuntu 12.04 "xmllint: використання libxml версії 20708"

Я натрапив на рішення, яке є стабільним, коротким і працює на багатьох платформах, і це вже дозріло - це вбудований в рубін lib rexml:

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

Що мене надихнуло знайти цю статтю:


1
Це навіть вужчі критерії, ніж питання, тому воно, безумовно, відповідає як відповідь. Я впевнений, що багатьом людям, які зіткнулися з вашою ситуацією, допоможуть ваші дослідження. Я зберігаю xmlstarletяк прийняту відповідь, тому що вона відповідає моїм більш широким критеріям і справді акуратна . Але, мабуть, час від часу я буду використовувати ваше рішення.
клак

2
Я додам, що щоб уникнути цитат навколо результату , використовуйте putsзамість команди pRuby.
TomG

10

Saxon зробить це не тільки для XPath 2.0, але і для XQuery 1.0 та (у комерційній версії) 3.0. Це не як пакет Linux, а як jar файл. Синтаксис (який можна легко загорнути в простий сценарій)

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

ОНОВЛЕННЯ 2020 року

Saxon 10.0 включає інструмент Gizmo, який можна використовувати в інтерактивному режимі або в пакетному пакеті з командного рядка. Наприклад

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

SaxonB є в пакеті Ubuntu, libsaxonb-javaале якщо я запускаю, saxonb-xquery -qs://element/@attribute -s:filename.xmlя отримую SENR0001: Cannot serialize a free-standing attribute nodeтаку ж проблему, як і, наприклад xml_grep.
clacke

3
Якщо ви хочете побачити повну інформацію про вузол атрибутів, обраний цим запитом, скористайтеся параметром -wrap у командному рядку. Якщо ви просто хочете значення рядка атрибута, додайте / string () до запиту.
Майкл Кей

Дякую. Додавання / string () наближається. Але він виводить заголовок XML і розміщує всі результати в одному рядку, тому сигари досі немає.
клак

2
Якщо ви не хочете заголовка XML, додайте параметр! Method = text.
Майкл Кей

Щоб використовувати простір імен, додайте його -qsтак:'-qs:declare namespace mets="http://www.loc.gov/METS/";/mets:mets/mets:dmdSec'
igo

5

Можливо, вас також зацікавить xsh . У ньому є інтерактивний режим, де ви можете робити все, що завгодно, з документом:

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

Здається, він не доступний як пакет, принаймні, не в Ubuntu.
clacke

1
@clacke: Це не так, але його можна встановити з CPAN cpan XML::XSH2.
choroba

@choroba, я спробував це в OS X, але він не вдався встановити, з якоюсь помилкою makefile.
cnst

@cnst: У вас встановлений XML :: LibXML?
choroba

@choroba, я не знаю; але моя думка в тому, що cpan XML::XSH2нічого не вдається встановити.
cnst

5

Відповідь Клаке чудова, але я думаю, що працює лише в тому випадку, якщо ваш джерело добре сформований XML, а не нормальний HTML.

Отже, щоб зробити те ж саме для звичайного веб-вмісту - HTML-документи, які не обов'язково мають добре сформований 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'))"

І замість цього використовувати html5lib (щоб переконатися, що ви маєте таку саму поведінку розбору, як веб-браузери - тому що, як і аналізатори браузера, html5lib відповідає вимогам розбору в специфікації 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'))

Так, я переконався, що XPath передбачає XML. Ця відповідь є гарним доповненням до інших тут, і дякую, що повідомили мені про html5lib!
клак

3

Аналогічно відповідям Майка та Клакке, ось однорівень python (використовуючи python> = 2.5), щоб отримати версію збірки з файлу pom.xml, який оточує той факт, що у файлах pom.xml зазвичай немає dtd або простір імен за замовчуванням, тому не виглядайте добре сформованим для 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)"

Тестується на Mac і Linux і не потребує встановлення додаткових пакетів.


2
Я цим сьогодні користувався! Наші сервери побудови не мали ні lxmlні xmllint, ні навіть Ruby. У дусі формату у власній відповіді я написав це як python3 -c "from xml.etree.ElementTree import parse; from sys import stdin; print(parse(stdin).find('.//element[subelement=\"value\"]/othersubelement').text)" <<< "$variable_containing_xml"у баші. .getroot()не здається необхідним.
клак

2

Окрім XML :: XSH та XML :: XSH2 є деякі grepподібні утиліти, які висмоктують як App::xml_grep2і XML::Twig(що включає, xml_grepа не xml_grep2). Вони можуть бути дуже корисними при роботі з великими або численними XML-файлами для швидких ліній або Makefileмішеней. XML::Twigособливо приємно працювати з perlсценарієм підходу, коли ви хочете трохи більше обробити, ніж ваш $SHELLтаxmllint xstlproc пропозиція.

Схема нумерації у назвах додатків вказує на те, що версії "2" є новішою / пізнішою версією по суті того ж інструменту, який може потребувати більш пізніх версій інших модулів (або самого perlсебе).


xml_grep2 -t //element@attribute filename.xmlпрацює і робить те, що я очікую від цього ( xml_grep --root //element@attribute --text_only filename.xmlвсе ще не робить, повертає помилку "нерозпізнаний вираз"). Чудово!
clacke

Про що xml_grep --pretty_print --root '//element[@attribute]' --text_only filename.xml? Не впевнений, що там відбувається або що []в цьому випадку говорить XPath , але оточення @attributeз квадратними дужками працює для xml_grepі xml_grep2.
Г. Ціто

Я маю на увазі //element/@attribute, ні //element@attribute. Неможливо редагувати його, але залишити його там, а не видалити + замінити, щоб не плутати історію цього обговорення.
клак

//element[@attribute]вибирає елементи типу, elementякі мають атрибут attribute. Я не хочу елемент, а лише атрибут. <element attribute='foo'/>повинен дати мені foo, не повний <element attribute='foo'/>.
клак

... і --text_onlyв цьому контексті видає мені порожній рядок у випадку такого елемента, як <element attribute='foo'/>текст без вузла всередині.
клак


2

Я спробував кілька утиліт командного рядка XPath, і коли зрозумів, що витрачаю занадто багато часу на гуглінг і з'ясовуючи, як вони працюють, тож написав найпростіший можливий аналізатор XPath в Python, який зробив те, що мені потрібно.

Сценарій, наведений нижче, показує значення рядка, якщо вираз XPath оцінює рядок або показує весь підмережу XML, якщо результатом є вузол:

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

Він використовує lxml- швидкий XML-аналізатор, написаний на C, який не входить до стандартної бібліотеки python. Встановіть його за допомогою pip install lxml. На Linux / OSX може знадобитися префіксація зsudo .

Використання:

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

lxml також може приймати URL як вхід:

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

Витягніть атрибут URL під вузлом корпусу, тобто <enclosure url="http:...""..>):

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

Xpath в Google Chrome

Як неспоріднене бічне зауваження: Якщо випадково ви хочете запустити вираз XPath проти розмітки веб-сторінки, ви можете зробити це прямо з розроблених файлів Chrome: клацніть правою кнопкою миші сторінку в Chrome> виберіть «Оглянути», а потім у DevTools консоль вставити ваш XPath вираз як $x("//spam/eggs") .

Знайдіть усіх авторів на цій сторінці:

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

Не однолінійний, і lxmlвже згадувалося у двох інших відповідях за роки до вашого.
клак

2

Ось один випадок використання xmlstarlet для отримання даних з вкладених елементів elem1, elem2 до одного рядка тексту з цього типу XML (також показано, як обробляти простори імен):

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

Вихід буде

0.586 10.586 cue-in outro

У цьому фрагменті -m відповідає вкладеному elem2, -v виводить значення атрибутів (з виразами та відносною адресацією), -o буквальному тексті, -n додає новий рядок:

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

Якщо для elem1 потрібно більше атрибутів, можна зробити це так (також показуючи функцію 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

Зауважте, що (IMO непотрібне) ускладнення з просторами імен (ns, оголошено з -N), що мало не відмовився від xpath та xmlstarlet та написав швидкий спеціальний перетворювач.


xmlstarlet чудовий, але прийнята і основна відповідь на рейтинг вже згадує про це. Інформація про те, як поводитися з просторами імен, могла б бути доречною як коментар, якщо вона взагалі є. Усі, хто стикається з проблемами з просторами імен та xmlstarlet, можуть знайти відмінну дискусію в документації
clacke

2
Звичайно, @clacke, xmlstarlet вже згадувалося кілька разів, але також, що це важко зрозуміти і недодокументовано. Я цілу годину здогадувався, як отримати інформацію з вкладених елементів. Я б хотів, щоб у мене був такий приклад, тому я публікую його тут, щоб уникнути інших, які втрачають час (і приклад занадто довгий для коментарів).
diemo

2

Мій скрипт Python xgrep.py робить саме це. Щоб шукати всі атрибути attributeелементів elementу файлах filename.xml ..., слід запустити його наступним чином:

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

Існують різні перемикачі для управління результатами, наприклад, -cдля підрахунку матчів, -iдля відступу збіжних частин та -lдля виведення тільки імен файлів.

Сценарій недоступний як пакет Debian або Ubuntu, але всі його залежності є.


А ви хостите на sourcehut! Приємно!
clacke

1

Оскільки цей проект, очевидно, досить новий, перевірте https://github.com/jeffbr13/xq , здається, обгортка навколо lxml, але це все, що вам дійсно потрібно (і розміщено спеціальні рішення з використанням lxml в інших відповідях)


1

Я не був задоволений однофайлами Python для запитів HTML XPath, тому написав власний. Передбачає, що ви встановили python-lxmlпакет або запустили 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 }

Як тільки у вас є, ви можете використовувати його, як у цьому прикладі:

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

0

Встановіть базу даних BaseX , а потім скористайтеся її "автономним режимом командного рядка" таким чином:

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

або

basex -i filename.xml //element@attribute

Мова запитів - це насправді XQuery (3.0), а не XPath, але оскільки XQuery є набором XPath, ви можете використовувати XPath запити, не помічаючи ніколи.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.