Como executar one-liners XPath a partir do shell?


192

Existe um pacote lá fora, para o Ubuntu e / ou CentOS, que possui uma ferramenta de linha de comando que pode executar um liner XPath como foo //element@attribute filename.xmlou foo //element@attribute < filename.xmle retornar os resultados linha por linha?

Estou procurando por algo que me permita apenas apt-get install fooou yum install fooentão funcione imediatamente, sem invólucros ou outras adaptações necessárias.

Aqui estão alguns exemplos de coisas que se aproximam:

Nokogiri. Se eu escrever este wrapper, poderia chamá-lo da maneira descrita acima:

#!/usr/bin/ruby

require 'nokogiri'

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

XML :: XPath. Trabalharia com este wrapper:

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

xpathfrom XML :: XPath retorna muito ruído -- NODE --e attribute = "value".

xml_grep from XML :: Twig não pode manipular expressões que não retornam elementos; portanto, não pode ser usado para extrair valores de atributos sem processamento adicional.

EDITAR:

echo cat //element/@attribute | xmllint --shell filename.xmlretorna ruído semelhante a xpath.

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

xmllint --xpath 'string(//element/@attribute)' filename.xml retorna o que eu quero, mas apenas para a primeira partida.

Para outra solução que quase satisfaça a pergunta, aqui está um XSLT que pode ser usado para avaliar expressões XPath arbitrárias (requer dyn: avalie o suporte no processador 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>

Corra com xsltproc --stringparam pattern //element/@attribute --stringparam value . arbitrary-xpath.xslt filename.xml.


+1 para uma boa causa e para a de brainstorming sobre encontrar uma maneira simples e confiável para imprimir resultado múltipla cada um em uma nova linha
Gilles Quenot

1
Observe que o "ruído" de xpathestá em STDERR e não em STDOUT.
precisa saber é o seguinte

@ miken32 Não. Eu queria apenas o valor da saída. hastebin.com/ekarexumeg.bash
clacke

Respostas:


271

Você deve tentar estas ferramentas:

  • xmlstarlet : pode editar, selecionar, transformar ... Não instalado por padrão, xpath1
  • xmllint: geralmente instalado por padrão com libxml2-utils, xpath1 (verifique meu wrapper para --xpathativar releases muito antigos e saída delimitada por novas linhas (v <2.9.9)
  • xpath: instalado via módulo do perl XML::XPath, xpath1
  • xml_grep: instalado via módulo do perl XML::Twig, xpath1 (uso limitado do xpath)
  • xidel: xpath3
  • saxon-lint : meu próprio projeto, invólucro da biblioteca Saxon-HE Java de @Michael Kay, xpath3

xmllintvem com libxml2-utils(pode ser usado como shell interativo com o --shellswitch)

xmlstarleté xmlstarlet.

xpath vem com o módulo do perl XML::Xpath

xml_grep vem com o módulo do perl XML::Twig

xidel é xidel

saxon-lintusando SaxonHE 9.6 , XPath 3.x (+ compatibilidade retroativa)

Ex:

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
Excelente! xmlstarlet sel -T -t -m '//element/@attribute' -v '.' -n filename.xmlfaz exatamente o que eu quero!
clacke

2
Nota: havia rumores de que xmlstarlet foi abandonado, mas agora está em desenvolvimento ativo novamente.
clacke

6
Nota: Algumas versões mais antigas xmllintdo não suportam argumentos de linha de comando --xpath, mas a maioria parece suportar --shell. Saída mais suja, mas ainda útil em um encadernação.
Kevinarpe 13/05

Ainda estou com problemas para consultar o conteúdo do nó, não um atributo. Alguém pode dar um exemplo disso? Por alguma razão, eu ainda acho o xmlstarlet difícil de descobrir e acerto entre a correspondência, o valor, a raiz para visualizar apenas a estrutura do documento e etc. Mesmo com o primeiro sel -t -m ... -v ...exemplo desta página: arstechnica.com/information-technology/2005 / 11 / linux-20051115/2 , correspondendo a todos, exceto o último nó, e salvando esse para a expressão de valor como meu caso de uso, ainda não consigo obtê-lo, apenas recebo uma saída em branco ..
Pysis

nice one da versão do xpath - Eu tinha acabado de passar por essa limitação do resto excelente xmllint
JonnyRaa

20

Você também pode experimentar o meu Xidel . Ele não está em um pacote no repositório, mas você pode simplesmente fazer o download da página da Web (não possui dependências).

Possui uma sintaxe simples para esta tarefa:

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

E é uma das raras dessas ferramentas que oferece suporte ao XPath 2.


2
O Xidel parece bem legal, embora você provavelmente deva mencionar que também é o autor desta ferramenta que você recomenda.
FrustratedWithFormsDesigner

1
Saxon e Saxon fiapos uso xpath3;)
Gilles Quenot

O Xidel (0..8.win32.zip) aparece como tendo malware no Virustotal. Então tente a seu próprio risco virustotal.com/#/file/...
JGFMK

grande - Eu estou indo para adicionar xidel à minha caixa de ferramenta da chave pessoal
maoizm

15

Um pacote que provavelmente está instalado em um sistema já é python-lxml. Nesse caso, isso é possível sem a instalação de nenhum pacote extra:

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

1
Como passar o nome do arquivo?
Ramakrishnan Kannan

4
Isso funciona stdin. Isso elimina a necessidade de incluir open()e close()em uma linha já bastante longa. Para analisar um arquivo, basta executarpython -c "from lxml.etree import parse; from sys import stdin; print '\n'.join(parse(stdin).xpath('//element/@attribute'))" < my_file.xml e deixar seu shell manipular a pesquisa, a abertura e o fechamento do arquivo.
Clacke

10

Na minha pesquisa para consultar arquivos maven pom.xml, corri através dessa pergunta. No entanto, eu tinha as seguintes limitações:

  • deve executar multiplataforma.
  • deve existir em todas as principais distribuições linux sem nenhuma instalação de módulo adicional
  • deve manipular arquivos xml complexos, como arquivos maven pom.xml
  • sintaxe simples

Eu tentei muitas das opções acima sem sucesso:

  • python lxml.etree não faz parte da distribuição python padrão
  • O xml.etree é, mas não lida bem com arquivos complexos do maven pom.xml, não se aprofundou o suficiente
  • python xml.etree não manipula arquivos maven pom.xml por motivo desconhecido
  • O xmllint também não funciona, despejos de núcleo frequentemente no ubuntu 12.04 "xmllint: using libxml version 20708"

A solução que encontrei, que é estável, curta e funciona em muitas plataformas e que é madura, é a lib do rexml embutida no ruby:

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

O que me inspirou a encontrar este foi os seguintes artigos:


1
Esse é um critério ainda mais restrito do que a pergunta, por isso definitivamente se encaixa como resposta. Tenho certeza de que muitas pessoas que se depararam com a sua situação serão ajudadas por sua pesquisa. Eu estou mantendo xmlstarletcomo a resposta aceita, porque se encaixa nos meus critérios mais amplos e é realmente legal . Mas provavelmente terei uso da sua solução de tempos em tempos.
Clacke 14/05

2
Eu acrescentaria que, para evitar aspas no resultado , use em putsvez de pno comando Ruby.
TomG

10

O Saxon fará isso não apenas no XPath 2.0, mas também no XQuery 1.0 e (na versão comercial) 3.0. Ele não vem como um pacote Linux, mas como um arquivo jar. A sintaxe (que você pode envolver facilmente em um script simples) é

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

ATUALIZAÇÃO 2020

O Saxon 10.0 inclui a ferramenta Gizmo, que pode ser usada interativamente ou em lote na linha de comando. Por exemplo

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

SaxonB está no Ubuntu, pacote libsaxonb-java, mas se eu executar saxonb-xquery -qs://element/@attribute -s:filename.xmleu recebo SENR0001: Cannot serialize a free-standing attribute node, mesmo problema que com o eg xml_grep.
clacke

3
Se você deseja ver detalhes completos do nó do atributo selecionado por esta consulta, use a opção -wrap na linha de comandos. Se você deseja apenas o valor da string do atributo, adicione / string () à consulta.
Michael Kay

Obrigado. Adicionar / string () se aproxima. Mas ele gera um cabeçalho XML e coloca todos os resultados em uma linha, então ainda não há charuto.
clacke

2
Se você não deseja um cabeçalho XML, adicione a opção! Method = text.
Michael Kay

Para usar o namespace adicioná-la a -qsgostar deste:'-qs:declare namespace mets="http://www.loc.gov/METS/";/mets:mets/mets:dmdSec'
igo

5

Você também pode estar interessado em xsh . Possui um modo interativo onde você pode fazer o que quiser com o documento:

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

Não parece estar disponível como um pacote, pelo menos não no Ubuntu.
clacke

1
@ Clacke: Não é, mas pode ser instalado a partir do CPAN cpan XML::XSH2.
181313 choroba

@choroba, eu tentei isso no OS X, mas ele não foi instalado, com algum tipo de erro de makefile.
CNST

@cnst: Você possui o XML :: LibXML instalado?
choroba

@choroba, eu não sei; mas meu argumento é que, cpan XML::XSH2falha ao instalar qualquer coisa.
CNST

5

A resposta de Clacke é ótima, mas acho que só funciona se sua fonte for XML bem formada, não HTML normal.

Portanto, faça o mesmo com o conteúdo normal da Web - documentos HTML que não são necessariamente XML bem formados:

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

E, em vez disso, use o html5lib (para garantir que você obtenha o mesmo comportamento de análise dos navegadores da Web - porque, como os analisadores de navegador, o html5lib está em conformidade com os requisitos de análise na especificação 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'))

Sim, eu me apaixonei por minha própria suposição na pergunta, que XPath implica XML. Esta resposta é um bom complemento para os outros aqui, e obrigado por me informar sobre o html5lib!
Clacke

3

Semelhante às respostas de Mike e Clacke, aqui está o python one-liner (usando python> = 2.5) para obter a versão de compilação de um arquivo pom.xml que contorna o fato de que os arquivos pom.xml normalmente não possuem um dtd ou namespace padrão, portanto, não pareça bem formado para 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)"

Testado no Mac e Linux e não requer a instalação de nenhum pacote extra.


2
Eu usei isso hoje! Nossos servidores de build não tinham nem lxmlnem xmllintnem Ruby. No espírito do formato em minha própria resposta , escrevi como python3 -c "from xml.etree.ElementTree import parse; from sys import stdin; print(parse(stdin).find('.//element[subelement=\"value\"]/othersubelement').text)" <<< "$variable_containing_xml"em bash. .getroot()não parece necessário.
clacke

2

Além do XML :: XSH e XML :: XSH2, existem alguns greputilitários similares que sugam como App::xml_grep2e XML::Twig(que inclui em xml_grepvez de xml_grep2). Isso pode ser bastante útil ao trabalhar em arquivos XML grandes ou numerosos para oneliners ou Makefiledestinos rápidos . XML::Twigé especialmente bom trabalhar com uma perlabordagem de script quando você deseja um processamento um pouco mais do que o seu $SHELLe a xmllint xstlprocoferta.

O esquema de numeração nos nomes dos aplicativos indica que as versões "2" são uma versão mais nova / posterior da essencialmente a mesma ferramenta que pode exigir versões posteriores de outros módulos (ou de perlsi mesmo).


xml_grep2 -t //element@attribute filename.xmlfunciona e faz o que eu espero ( xml_grep --root //element@attribute --text_only filename.xmlainda não funciona, retorna um erro de "expressão não reconhecida"). Ótimo!
clacke

Que tal xml_grep --pretty_print --root '//element[@attribute]' --text_only filename.xml? Não tenho certeza do que está acontecendo lá ou do que o XPath diz sobre []este caso, mas cercar um @attributeentre colchetes funciona para xml_grepe xml_grep2.
G. Cito

Quero dizer //element/@attribute, não //element@attribute. Não é possível editá-lo aparentemente, mas deixá-lo lá em vez de excluir + substituir para não confundir o histórico desta discussão.
clacke

//element[@attribute]seleciona elementos do tipo elementque possuem um atributo attribute. Eu não quero o elemento, apenas o atributo. <element attribute='foo'/>deve me dar foo, não o total <element attribute='foo'/>.
clacke

... e --text_onlynesse contexto me fornece a string vazia no caso de um elemento como <element attribute='foo'/>sem nó de texto dentro.
clacke


2

Tentei alguns utilitários XPath da linha de comando e, quando percebi que estava gastando muito tempo pesquisando e descobrindo como eles funcionavam, escrevi o analisador XPath mais simples possível em Python, que fez o que eu precisava.

O script abaixo mostra o valor da sequência se a expressão XPath for avaliada como uma sequência ou mostra o subnó XML inteiro se o resultado for um nó:

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

Ele usa lxml- um analisador XML rápido, escrito em C, que não está incluído na biblioteca python padrão. Instale-o com pip install lxml. No Linux / OSX, pode ser necessário prefixar com sudo.

Uso:

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

O lxml também pode aceitar um URL como entrada:

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

Extraia o atributo url em um nó do gabinete, ou seja <enclosure url="http:...""..>):

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

Xpath no Google Chrome

Como uma observação lateral não relacionada: se por acaso você desejar executar uma expressão XPath na marcação de uma página da Web, poderá fazê-la diretamente nos devtools do Chrome: clique com o botão direito do mouse na página do Chrome> selecione Inspecionar e, em seguida, no DevTools console cole sua expressão XPath como $x("//spam/eggs") .

Obter todos os autores nesta página:

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

Não é uma linha e lxmljá foi mencionado em duas outras respostas anos antes da sua.
clacke

2

Aqui está um caso de uso xmlstarlet para extrair dados de elementos aninhados elem1, elem2 para uma linha de texto desse tipo de XML (mostrando também como lidar com espaços para nome):

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

A saída será

0.586 10.586 cue-in outro

Nesse trecho, -m corresponde ao elem2 aninhado, -v gera valores de atributo (com expressões e endereçamento relativo), -o texto literal, -n adiciona uma nova linha:

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

Se forem necessários mais atributos do elem1, é possível fazer o seguinte (também mostrando a função 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

Observe a complicação (desnecessária da IMO) com os espaços para nome (ns, declarados com -N), que quase me deixaram desistir do xpath e xmlstarlet e escrever um rápido conversor ad-hoc.


O xmlstarlet é ótimo, mas a resposta do ranking principal e aceita já o menciona. As informações sobre como lidar com espaços para nome podem ter sido relevantes como um comentário, se houver. Qualquer um correndo em problemas com namespaces e xmlstarlet pode encontrar uma excelente discussão na documentação
clacke

2
Certamente, @clacke, xmlstarlet já foi mencionado várias vezes, mas também é difícil de entender e tem documentação insuficiente. Fiquei pensando por uma hora como obter informações de elementos aninhados. Eu gostaria de ter tido esse exemplo, é por isso que estou postando aqui para evitar que outros percam tempo (e o exemplo é muito longo para um comentário).
Diem

2

Meu script Python xgrep.py faz exatamente isso. Para procurar todos os atributos attributedos elementos elementnos arquivos filename.xml ..., você deve executá-lo da seguinte maneira:

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

Existem várias opções para controlar a saída, como -ccontar contagens, -irecuar as peças correspondentes e-l saída apenas de nomes de arquivos.

O script não está disponível como um pacote Debian ou Ubuntu, mas todas as suas dependências são.


E você está hospedando no sourcehut! Agradável!
Clacke

1

Como esse projeto é aparentemente relativamente novo, confira https://github.com/jeffbr13/xq , parece um invólucro lxml, mas é tudo o que você realmente precisa (e postou soluções ad hoc usando lxml em outras respostas também)


1

Eu não estava feliz com as linhas de código Python para consultas em HTML XPath, então escrevi as minhas. Supõe que você instalou o python-lxmlpacote ou executou 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 }

Depois de ter, você pode usá-lo como neste exemplo:

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

0

Instale o banco de dados BaseX e use o "modo de linha de comando independente" desta maneira:

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

ou

basex -i filename.xml //element@attribute

A linguagem da consulta é na verdade XQuery (3.0), não XPath, mas como o XQuery é um superconjunto do XPath, você pode usar as consultas XPath sem perceber.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.