Как выполнить XPath однострочно из оболочки?

192

Существует ли какой-нибудь пакет для Ubuntu и / или CentOS, в котором есть инструмент командной строки, который может выполнять однострочное выполнение XPath foo //element@attribute filename.xmlили foo //element@attribute < filename.xmlвозвращать результаты построчно?

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

Вот несколько примеров таких вещей:

Nokogiri. Если я напишу эту оболочку, я мог бы вызвать оболочку так, как описано выше:

#!/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: define в процессоре 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.

clacke
источник
+1 для хорошего вопроса и для мозгового штурма о поиске простого и надежного способа напечатать несколько результатов каждый на новой
строке
1
Обратите внимание, что «шум» от xpathнаходится на STDERR, а не на STDOUT.
miken32
@ miken32 Нет. Я хотел только значение для вывода. hastebin.com/ekarexumeg.bash
клик

Ответы:

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 @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 и правильно определить соответствие, значение, корень для простого просмотра структуры документа и т. Д. Даже с первым sel -t -m ... -v ...примером на этой странице: arstechnica.com/information-technology/2005 / 11 / linux-20051115/2 , сопоставляя все, кроме последнего узла, и сохраняя его для выражения значения, такого как мой
сценарий
хороший вариант для версии xpath - я бы просто наткнулся на это ограничение отличного в противном случае xmllint
JonnyRaa
20

Вы также можете попробовать мой Xidel . Его нет в пакете в репозитории, но вы можете просто загрузить его с веб-страницы (он не имеет зависимостей).

Он имеет простой синтаксис для этой задачи:

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

И это один из редких инструментов, который поддерживает XPath 2.

BeniBela
источник
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'))"
clacke
источник
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и пусть ваша оболочка обработает поиск, открытие и закрытие файла.
глэк
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»

Решение, с которым я столкнулся, является стабильным, коротким и работает на многих платформах, а также является зрелым, это встроенная в ruby ​​библиотека rexml lib:

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

Что вдохновило меня на поиск этой статьи, так это следующие статьи:

Майк
источник
1
Это даже более узкие критерии, чем вопрос, поэтому он определенно подходит в качестве ответа. Я уверен, что многим людям, которые столкнулись с вашей ситуацией, поможет ваше исследование. Я придерживаюсь xmlstarletпринятого ответа, потому что он соответствует моим более широким критериям и действительно опрятен . Но я, вероятно, буду время от времени использовать ваше решение.
Clacke
2
Я бы добавил, что, чтобы избежать кавычек вокруг результата , используйте putsвместо команды pRuby.
tooomg
10

Саксон сделает это не только для 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.
Клак
3
Если вы хотите увидеть полную информацию об узле атрибута, выбранном этим запросом, используйте параметр -wrap в командной строке. Если вы просто хотите получить строковое значение атрибута, добавьте / string () к запросу.
Майкл Кей
Спасибо. Добавление / string () становится ближе. Но он выводит заголовок XML и помещает все результаты в одну строку, так что сигары по-прежнему нет.
Clacke
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() ;
choroba
источник
Похоже, он не доступен как пакет, по крайней мере, не в Ubuntu.
глухой
1
@clacke: это не так, но его можно установить из CPAN cpan XML::XSH2.
Чороба
@choroba, я пробовал это на OS X, но его не удалось установить, с какой-то ошибкой make-файла.
CNST
@cnst: у вас установлен XML :: LibXML?
Чороба
@choroba, я не знаю; но я хочу сказать, что cpan XML::XSH2ничего не устанавливается.
CNST
5

Ответ clacke отличный, но я думаю, что он работает только в том случае, если ваш источник - правильно сформированный 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'))
sideshowbarker
источник
Да, я решил, что в XPath подразумевается XML. Этот ответ является хорошим дополнением к остальным здесь, и спасибо, что сообщили мне о html5lib!
Clacke
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"в bash. .getroot()не кажется необходимым.
Клак
2

В дополнение к XML :: XSH и XML :: XSH2 есть некоторые grepподобные утилиты suck as 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до сих пор нет, возвращает ошибку «нераспознанное выражение»). Большой!
клик
Как насчет xml_grep --pretty_print --root '//element[@attribute]' --text_only filename.xml? Не уверен, что там происходит или о чем говорит XPath []в этом случае, но окружение @attributeс квадратными скобками работает для xml_grepи xml_grep2.
Г. Сито
Я имею в виду //element/@attribute, нет //element@attribute. Очевидно, что редактировать его не могу, но оставив его там вместо удаления + замены, чтобы не запутать историю этого обсуждения.
Clacke
//element[@attribute]выбирает элементы типа, elementкоторые имеют атрибут attribute. Я не хочу элемент, только атрибут. <element attribute='foo'/>должен дать мне foo, а не полный <element attribute='foo'/>.
Clacke
... и --text_onlyв этом контексте дает мне пустую строку в случае элемента, как <element attribute='foo'/>без текстового узла внутри.
Clacke
2

Стоит упомянуть, что сам nokogiri поставляется с инструментом командной строки, который должен быть установлен вместе с gem install nokogiri.

Вы можете найти этот пост полезным .

Джефф Никсон
источник
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 devtools: щелкните правой кнопкой мыши страницу в Chrome> выберите Inspect, а затем в DevTools консоль вставьте ваше выражение XPath как $x("//spam/eggs").

Получить всех авторов на этой странице:

$x("//*[@class='user-details']/a/text()")
ccpizza
источник
Ни одной строки, и 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, и написание быстрого специального конвертера.

diemo
источник
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! Ницца!
Диск
1

Поскольку этот проект, по-видимому, довольно новый, ознакомьтесь с https://github.com/jeffbr13/xq, который , похоже, является оберткой lxml, но это все, что вам действительно нужно (и опубликовал специальные решения, использующие lxml и в других ответах).

mgrandi
источник
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
d33tah
источник
0

Установите базу данных BaseX , затем используйте ее «автономный режим командной строки» следующим образом:

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

или

basex -i filename.xml //element@attribute

На самом деле языком запросов является XQuery (3.0), а не XPath, но поскольку XQuery является расширенным набором XPath, вы можете использовать запросы XPath, даже не заметив этого.

Igneus
источник