¿Cómo ejecutar XPath one-liners desde shell?

192

¿Existe un paquete para Ubuntu y / o CentOS que tenga una herramienta de línea de comandos que pueda ejecutar un XPath de una línea como foo //element@attribute filename.xmlo foo //element@attribute < filename.xmly devolver los resultados línea por línea?

Estoy buscando algo que me permita simplemente apt-get install fooo yum install foosimplemente funcionar de inmediato, sin envoltorios u otra adaptación necesaria.

Aquí hay algunos ejemplos de cosas que se acercan:

Nokogiri. Si escribo este contenedor, podría llamar al contenedor de la manera descrita anteriormente:

#!/usr/bin/ruby

require 'nokogiri'

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

XML :: XPath. Funcionaría con este contenedor:

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

xpathde XML :: XPath devuelve demasiado ruido -- NODE --y attribute = "value".

xml_grep from XML :: Twig no puede manejar expresiones que no devuelven elementos, por lo que no se puede usar para extraer valores de atributos sin más procesamiento.

EDITAR:

echo cat //element/@attribute | xmllint --shell filename.xmldevuelve ruido similar a xpath.

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

xmllint --xpath 'string(//element/@attribute)' filename.xml devuelve lo que quiero, pero solo para el primer partido.

Para otra solución que casi satisface la pregunta, aquí hay un XSLT que se puede usar para evaluar expresiones XPath arbitrarias (requiere dyn: evaluar el soporte en el procesador 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>

Corre con xsltproc --stringparam pattern //element/@attribute --stringparam value . arbitrary-xpath.xslt filename.xml.

clacke
fuente
+1 para una buena pregunta y para la lluvia de ideas sobre cómo encontrar una manera simple y confiable de imprimir múltiples resultados cada uno en una nueva línea
Gilles Quenot
1
Tenga en cuenta que el "ruido" de xpathestá en STDERR y no en STDOUT.
miken32
@ miken32 No. Solo quería el valor de salida. hastebin.com/ekarexumeg.bash
clacke 05 de

Respuestas:

271

Deberías probar estas herramientas:

  • xmlstarlet : puede editar, seleccionar, transformar ... No está instalado de forma predeterminada, xpath1
  • xmllint: a menudo se instala de forma predeterminada con libxml2-utilsxpath1 (compruebe que mi contenedor tenga --xpathactivadas las versiones muy antiguas y la salida delimitada por líneas nuevas (v <2.9.9)
  • xpath: instalado a través del módulo de perl XML::XPath, xpath1
  • xml_grep: instalado a través del módulo de Perl XML::Twig, xpath1 (uso limitado de xpath)
  • xidel: xpath3
  • saxon-lint : mi propio proyecto, envoltorio sobre la biblioteca Java de Saxon-HE de @Michael Kay, xpath3

xmllintviene con libxml2-utils(se puede usar como shell interactivo con el --shellinterruptor)

xmlstarletes xmlstarlet.

xpath viene con el módulo de perl XML::Xpath

xml_grep viene con el módulo de perl XML::Twig

xidel es xidel

saxon-lintutilizando SaxonHE 9.6 , XPath 3.x (+ compatibilidad retro)

Ej:

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

.

Gilles Quenot
fuente
77
¡Excelente! xmlstarlet sel -T -t -m '//element/@attribute' -v '.' -n filename.xmlhace exactamente lo que quiero!
clacke
2
Nota: se rumoreaba que xmlstarlet era abandonado, pero ahora está en desarrollo activo nuevamente.
clacke
66
Nota: Algunas versiones anteriores de xmllintno admiten el argumento de la línea de comandos --xpath, pero la mayoría parece admitirlo --shell. Salida ligeramente más sucia, pero aún útil en un aprieto.
kevinarpe
Parece que todavía tengo problemas para consultar el contenido del nodo, no un atributo. ¿Alguien puede dar un ejemplo para eso? Por alguna razón, todavía encuentro que xmlstarlet es difícil de entender y acertar entre la coincidencia, el valor, la raíz para ver la estructura del documento, etc. Incluso con el primer sel -t -m ... -v ...ejemplo de esta página: arstechnica.com/information-technology/2005 / 11 / linux-20051115/2 , haciendo coincidir todo menos el último nodo y guardando ese para la expresión de valor como mi caso de uso, todavía parece que no puedo obtenerlo, solo obtengo un resultado en blanco ...
Pysis
bonita de la versión de XPath - Me basta con ejecutar en esta limitación de la por lo demás excelente xmllint
JonnyRaa
20

También puedes probar mi Xidel . No está en un paquete en el repositorio, pero puede descargarlo de la página web (no tiene dependencias).

Tiene una sintaxis simple para esta tarea:

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

Y es una de las pocas herramientas que admite XPath 2.

BeniBela
fuente
2
Xidel se ve muy bien, aunque probablemente deberías mencionar que tú también eres el autor de esta herramienta que recomiendas.
FrustratedWithFormsDesigner
1
Saxon y saxon-lint usan xpath3;)
Gilles Quenot
Xidel (0..8.win32.zip) aparece con malware en Virustotal. Intente bajo su propio riesgo virustotal.com/#/file/…
JGFMK
genial - voy a agregar xidel a mi caja de herramientas de llave personal
maoizm
15

Un paquete que es muy probable que ya esté instalado en un sistema ya lo es python-lxml. Si es así, esto es posible sin instalar ningún paquete adicional:

python -c "from lxml.etree import parse; from sys import stdin; print '\n'.join(parse(stdin).xpath('//element/@attribute'))"
clacke
fuente
1
¿Cómo pasar el nombre del archivo?
Ramakrishnan Kannan
44
Esto funciona en stdin. Eso elimina la necesidad de incluir open()y close()en una línea bastante larga. Para analizar un archivo simplemente ejecute python -c "from lxml.etree import parse; from sys import stdin; print '\n'.join(parse(stdin).xpath('//element/@attribute'))" < my_file.xmly deje que su shell maneje la búsqueda, apertura y cierre del archivo.
clacke
10

En mi búsqueda para consultar archivos maven pom.xml, me encontré con esta pregunta. Sin embargo, tenía las siguientes limitaciones:

  • debe correr multiplataforma.
  • debe existir en todas las distribuciones principales de Linux sin ninguna instalación de módulo adicional
  • debe manejar archivos xml complejos, como los archivos maven pom.xml
  • sintaxis simple

He intentado muchas de las anteriores sin éxito:

  • python lxml.etree no es parte de la distribución estándar de python
  • xml.etree es, pero no maneja bien, archivos complejos de pom.xml de maven, no ha cavado lo suficientemente
  • python xml.etree no maneja archivos maven pom.xml por razones desconocidas
  • xmllint tampoco funciona, los volcados de núcleo a menudo en ubuntu 12.04 "xmllint: usando libxml versión 20708"

La solución con la que me he encontrado que es estable, corta y que funciona en muchas plataformas y que es madura es el rexml lib incorporado en ruby:

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

Lo que me inspiró a encontrar este fue los siguientes artículos:

Miguel
fuente
1
Es un criterio aún más limitado que la pregunta, por lo que definitivamente cabe como respuesta. Estoy seguro de que su investigación ayudará a muchas personas que se encontraron con su situación. Me mantengo xmlstarletcomo la respuesta aceptada, porque se ajusta a mis criterios más amplios y es realmente ordenada . Pero probablemente tendré uso para su solución de vez en cuando.
clacke
2
Agregaría eso para evitar comillas alrededor del resultado , use en putslugar de pen el comando Ruby.
tooomg
10

Saxon hará esto no solo para XPath 2.0, sino también para XQuery 1.0 y (en la versión comercial) 3.0. No viene como un paquete de Linux, sino como un archivo jar. La sintaxis (que puede ajustarse fácilmente en un script simple) es

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

ACTUALIZACIÓN 2020

Saxon 10.0 incluye la herramienta Gizmo, que se puede utilizar de forma interactiva o por lotes desde la línea de comandos. Por ejemplo

java net.sf.saxon.Gizmo -s:source.xml
/>show //element/@attribute
/>quit
Michael Kay
fuente
SaxonB está en Ubuntu, paquete libsaxonb-java, pero si ejecuto saxonb-xquery -qs://element/@attribute -s:filename.xmlme sale el SENR0001: Cannot serialize a free-standing attribute nodemismo problema que con eg xml_grep.
clacke
3
Si desea ver todos los detalles del nodo de atributo seleccionado por esta consulta, use la opción -wrap en la línea de comando. Si solo desea el valor de cadena del atributo, agregue / string () a la consulta.
Michael Kay
Gracias. Agregar / string () se acerca. Pero genera un encabezado XML y pone todos los resultados en una fila, por lo que todavía no hay cigarros.
clacke
2
Si no desea un encabezado XML, agregue la opción! Method = text.
Michael Kay
Para utilizar espacio de nombres añadirlo a -qsdesea:'-qs:declare namespace mets="http://www.loc.gov/METS/";/mets:mets/mets:dmdSec'
igo
5

También te puede interesar xsh . Cuenta con un modo interactivo donde puede hacer lo que quiera con el documento:

open 1.xml ;
ls //element/@id ;
for //p[@class="first"] echo text() ;
choroba
fuente
No parece estar disponible como un paquete, al menos no en Ubuntu.
clacke
1
@clacke: no lo es, pero se puede instalar desde CPAN mediante cpan XML::XSH2.
choroba
@ choroba, lo intenté en OS X, pero no se pudo instalar, con algún tipo de error de archivo MAKE.
Cnst
@cnst: ¿Tiene instalado XML :: LibXML?
choroba
@ Choroba, no lo sé; pero mi punto es que cpan XML::XSH2no puede instalar nada.
Cnst
5

La respuesta de Clacke es excelente, pero creo que solo funciona si su fuente es XML bien formado, no HTML normal.

Entonces, para hacer lo mismo con el contenido web normal: documentos HTML que no son necesariamente XML bien formado:

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

Y en su lugar, use html5lib (para asegurarse de obtener el mismo comportamiento de análisis que los navegadores web, porque, al igual que los analizadores de navegador, html5lib cumple con los requisitos de análisis de la especificación 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'))
sidehowbarker
fuente
Sí, caí por mi propia suposición en la pregunta, que XPath implica XML. Esta respuesta es un buen complemento para los demás aquí, ¡y gracias por informarme sobre html5lib!
clacke
3

Al igual que las respuestas de Mike y Clacke, aquí está la línea única de Python (usando python> = 2.5) para obtener la versión de compilación de un archivo pom.xml que evita el hecho de que los archivos pom.xml normalmente no tienen un dtd o espacio de nombres predeterminado, por lo que no parece estar bien 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)"

Probado en Mac y Linux, y no requiere la instalación de ningún paquete adicional.

pdr
fuente
2
¡Usé esto hoy! Nuestros servidores de compilación no tenían ni lxmlni xmllintni siquiera Ruby. En el espíritu del formato en mi propia respuesta , lo escribí 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"en bash. .getroot()no parece necesario
clacke
2

Además de XML :: XSH y XML :: XSH2, existen algunas greputilidades similares que succionan como App::xml_grep2y XML::Twig(que incluye en xml_greplugar de xml_grep2). Estos pueden ser bastante útiles cuando se trabaja en archivos XML grandes o numerosos para líneas rápidas u Makefileobjetivos. XML::Twiges especialmente agradable trabajar con un perlenfoque de secuencias de comandos cuando desea un poco más de procesamiento que el suyo $SHELLy la xmllint xstlprocoferta.

El esquema de numeración en los nombres de las aplicaciones indica que las versiones "2" son versiones más nuevas / posteriores de esencialmente la misma herramienta que puede requerir versiones posteriores de otros módulos (o de perlsí misma).

G. Cito
fuente
xml_grep2 -t //element@attribute filename.xmlfunciona y hace lo que espero ( xml_grep --root //element@attribute --text_only filename.xmltodavía no, devuelve un error de "expresión no reconocida"). ¡Excelente!
clacke
¿Qué hay de xml_grep --pretty_print --root '//element[@attribute]' --text_only filename.xml? No estoy seguro de lo que está sucediendo allí o de lo que XPath dice []en este caso, pero rodearlo @attributecon corchetes funciona para xml_grepy xml_grep2.
G. Cito
Quiero decir que //element/@attributeno //element@attribute. Aparentemente no puedo editarlo, pero dejarlo allí en lugar de eliminar + reemplazar para no confundir el historial de esta discusión.
clacke
//element[@attribute]selecciona elementos de tipo elementque tienen un atributo attribute. No quiero el elemento, solo el atributo. <element attribute='foo'/>debería darme foo, no el completo <element attribute='foo'/>.
clacke
... y --text_onlyen ese contexto me da la cadena vacía en el caso de un elemento como <element attribute='foo'/>sin un nodo de texto dentro.
clacke
2

Vale la pena mencionar que el propio nokogiri se envía con una herramienta de línea de comando, que debe instalarse con gem install nokogiri.

Es posible que encuentre útil esta publicación de blog .

Geoff Nixon
fuente
2

He probado un par de utilidades XPath de línea de comandos y cuando me di cuenta de que estoy pasando demasiado tiempo buscando en Google y descubriendo cómo funcionan, escribí el analizador XPath más simple posible en Python que hizo lo que necesitaba.

El siguiente script muestra el valor de la cadena si la expresión XPath se evalúa como una cadena, o muestra todo el subnodo XML si el resultado es un nodo:

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

Utiliza lxml: un analizador XML rápido escrito en C que no está incluido en la biblioteca estándar de Python. Instalarlo con pip install lxml. En Linux / OSX podría necesitar prefijos con sudo.

Uso:

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

lxml también puede aceptar una URL como entrada:

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

Extraiga el atributo url debajo de un nodo de caja, es decir <enclosure url="http:...""..>):

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

Xpath en Google Chrome

Como nota al margen no relacionada: si por casualidad desea ejecutar una expresión XPath contra el marcado de una página web, puede hacerlo directamente desde las herramientas de desarrollo de Chrome: haga clic con el botón derecho en la página en Chrome> seleccione Inspeccionar y luego en DevTools consola pegue su expresión XPath como $x("//spam/eggs").

Obtenga todos los autores en esta página:

$x("//*[@class='user-details']/a/text()")
ccpizza
fuente
No es una línea, y lxmlya se mencionó en otras dos respuestas años antes que la suya.
clacke
2

Aquí hay un caso de uso de xmlstarlet para extraer datos de elementos anidados elem1, elem2 a una línea de texto de este tipo de XML (que también muestra cómo manejar espacios de nombres):

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

La salida será

0.586 10.586 cue-in outro

En este fragmento, -m coincide con los elementos anidados elem2, -v genera valores de atributo (con expresiones y direccionamiento relativo), -o texto literal, -n agrega una nueva línea:

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

Si se necesitan más atributos de elem1, uno puede hacerlo así (también mostrando la función 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

Tenga en cuenta la complicación (IMO innecesaria) con espacios de nombres (ns, declarados con -N), que casi me hizo renunciar a xpath y xmlstarlet, y escribir un convertidor ad-hoc rápido.

diemo
fuente
xmlstarlet es genial, pero la respuesta de clasificación principal y aceptada ya lo menciona. La información sobre cómo manejar los espacios de nombres podría haber sido relevante como comentario, si es que lo hizo. Cualquier persona que tenga problemas con espacios de nombres y xmlstarlet puede encontrar una excelente discusión en la documentación
clacke
2
Claro, @clacke, xmlstarlet ha sido mencionado varias veces, pero también es difícil de entender y está subdocumentado. Estuve adivinando durante una hora cómo obtener información de elementos anidados. Desearía haber tenido ese ejemplo, por eso lo estoy publicando aquí para evitar que otros pierdan tiempo (y el ejemplo es demasiado largo para un comentario).
diemo
2

Mi script de Python xgrep.py hace exactamente esto. Para buscar todos los atributos attributede los elementos elementen los archivos filename.xml ..., debe ejecutarlo de la siguiente manera:

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

Hay varios interruptores para controlar la salida, como -cpara contar coincidencias, -ipara sangrar las partes coincidentes y -lpara generar solo nombres de archivo.

El script no está disponible como paquete Debian o Ubuntu, pero todas sus dependencias sí lo están.

Andreas Nolda
fuente
¡Y estás alojando en sourcehut! ¡Agradable!
clacke
1

Dado que este proyecto es aparentemente bastante nuevo, consulte https://github.com/jeffbr13/xq , parece ser un envoltorio lxml, pero eso es todo lo que realmente necesita (y también publicó soluciones ad hoc usando lxml en otras respuestas)

mgrandi
fuente
1

No estaba contento con Python one-liners para consultas HTML XPath, así que escribí el mío. Asume que instaló el python-lxmlpaquete o ejecutó 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 }

Una vez que lo tenga, puede usarlo como en este ejemplo:

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

Instale la base de datos BaseX , luego use su "modo de línea de comandos independiente" como este:

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

o

basex -i filename.xml //element@attribute

El lenguaje de consulta es en realidad XQuery (3.0), no XPath, pero dado que XQuery es un superconjunto de XPath, puede usar consultas XPath sin darse cuenta.

igneus
fuente