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

126

Есть ли там пакет для 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 не может обрабатывать выражения, которые не возвращают элементы, поэтому их нельзя использовать для извлечения значений атрибутов без дальнейшей обработки.

EDIT:

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.

  • 0
    +1 для хорошего вопроса и для мозгового штурма о поиске простого и надежного способа напечатать несколько результатов каждый на новой строке
  • 1
    Обратите внимание, что «шум» от xpath находится на STDERR, а не на STDOUT.
Показать ещё 1 комментарий
Теги:
xpath
cross-platform

11 ответов

186
Лучший ответ

Вам следует попробовать следующие инструменты:

  • xmlstarlet
  • xmllint
  • saxon-lint (собственный проект)

xmllint поставляется с libxml2-utils (может использоваться как интерактивная оболочка с помощью переключателя --shell) xmlstarlet is xmlstarlet.

saxon-lint с помощью SaxonHE 9.6 является единственным, кто запускает XPath 3.x (+ ретро-совместимость), другие - XPath 1.x.

Пример:

xmllint --xpath '//element/@attribute' file.xml
xmlstarlet sel -t -v "//element/@attribute" file.xml
saxon-lint --xpath '//element/@attribute' file.xml
  • 5
    Отлично! xmlstarlet sel -T -t -m '//element/@attribute' -v '.' -n filename.xml делает именно то, что я хочу!
  • 1
    Примечание: xmlstarlet, по слухам, был заброшен, но сейчас снова находится в активной разработке.
Показать ещё 4 комментария
14

Один пакет, который, скорее всего, будет установлен в системе, уже есть python-lxml. Если это так, это возможно без установки дополнительного пакета:

python -c "from lxml.etree import parse; from sys import stdin; print '\n'.join(parse(stdin).xpath('//element/@attribute'))"
  • 0
    Как передать имя файла?
  • 2
    Это работает на 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 и пусть ваша оболочка обрабатывает поиск, открытие и закрытие файла.
14

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

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

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

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

  • 2
    Xidel выглядит довольно круто, хотя вы должны упомянуть, что вы также являетесь автором этого инструмента, который вы рекомендуете.
  • 1
    Саксонские и саксонские линты используют xpath3;)
Показать ещё 2 комментария
9

Saxon сделает это не только для XPath 2.0, но и для XQuery 1.0 и (в коммерческой версии) 3.0. Он не поставляется как пакет Linux, а как файл jar. Синтаксис (который можно легко обернуть простым script) - это

java net.sf.saxon.Query -s:source.xml -qs://element/attribute
  • 0
    SaxonB находится в Ubuntu, пакет libsaxonb-java , но если я запускаю saxonb-xquery -qs://element/@attribute -s:filename.xml я получаю SENR0001: Cannot serialize a free-standing attribute node , SENR0001: Cannot serialize a free-standing attribute node же проблема, как, например, xml_grep .
  • 3
    Если вы хотите увидеть полную информацию об узле атрибута, выбранном этим запросом, используйте параметр -wrap в командной строке. Если вы просто хотите получить строковое значение атрибута, добавьте / string () к запросу.
Показать ещё 3 комментария
6

В моем запросе для запроса 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: using libxml version 20708"

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

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

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

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

Вас также может заинтересовать xsh. Он имеет интерактивный режим, в котором вы можете делать все, что угодно, с документом:

open 1.xml ;
ls //element/@id ;
for //p[@class="first"] echo text() ;
  • 0
    Похоже, он не доступен в виде пакета, по крайней мере, не в Ubuntu.
  • 1
    @clacke: это не так, но его можно установить из CPAN с помощью cpan XML::XSH2 .
Показать ещё 4 комментария
3

ответы на вызовы - это здорово, но я думаю, что работает только в том случае, если ваш источник является хорошо сформированным XML, а не нормальным HTML.

Итак, чтобы сделать то же самое для обычного веб-контента - HTML-документы, которые arent обязательно правильно сформировали 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'))
  • 0
    Да, я решил, что в XPath подразумевается XML. Этот ответ является хорошим дополнением к остальным здесь, и спасибо, что сообщили мне о html5lib!
2

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

Возможно, вы найдете этот пост в блоге.

2

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

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

  • 0
    xml_grep2 -t //element@attribute filename.xml работает и делает то, что я ожидаю ( xml_grep --root //element@attribute --text_only filename.xml прежнему нет, возвращает ошибку «нераспознанное выражение»). Большой!
  • 0
    Как насчет xml_grep --pretty_print --root '//element[@attribute]' --text_only filename.xml ? Не уверен, что там происходит или что XPath говорит о [] в этом случае, но окружение @attribute квадратными скобками работает для xml_grep и xml_grep2 .
Показать ещё 4 комментария
1

Подобно ответам Mike и clacke, вот однострочный 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 серверах не было ни 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() не кажется необходимым.
0

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

В приведенном ниже script показано строковое значение, если выражение XPath оценивается как строка или отображает весь подзону XML, если результатом является node:

#!/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 в приложении node (т.е.):

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

Xpath в Google Chrome

Как несвязанная сторона примечания: если случайно вы хотите запустить выражение XPath против разметки веб-страницы, вы можете сделать это прямо из Chrome devtools: щелкните правой кнопкой мыши страницу в Chrome > выберите "Осмотр", а затем в консоли DevTools вставьте выражение XPath как $x("//spam/eggs").

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

$x("//*[@class='user-details']/a/text()")
  • 0
    Не в одну lxml , а lxml уже упоминался в двух других ответах за годы до вашего.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню