Как разобрать XML в Bash?
В идеале я хотел бы иметь возможность:
cat xhtmlfile.xhtml |
getElementViaXPath --path='/html/head/title' |
sed -e 's%(^<title>|</title>$)%%g' > titleOfXHTMLPage.txt
18 ответов
Это на самом деле просто объяснение ответа Yuzem, но я не чувствовал, что так много нужно редактировать кому-то другому, а комментарии не позволяют форматировать, так что...
rdom () { local IFS=\> ; read -d \< E C ;}
Давайте назовем это "read_dom" вместо "rdom", выделим его немного и используем более длинные переменные:
read_dom () {
local IFS=\>
read -d \< ENTITY CONTENT
}
Итак, он определяет функцию с именем read_dom. Первая строка делает IFS (разделитель входного поля) локальным для этой функции и изменяет его на>. Это означает, что когда вы читаете данные вместо того, чтобы автоматически разделяться на пробелы, табуляции или переводы строк, они разделяются на ">". В следующей строке написано, что нужно читать ввод из stdin, и вместо того, чтобы останавливаться на новой строке, останавливаться, когда вы видите символ "<" (флаг -d для deliminator). То, что читается, затем разделяется с использованием IFS и присваивается переменной ENTITY и CONTENT. Итак, возьмите следующее:
<tag>value</tag>
Первый звонок read_dom
получить пустую строку (так как '<' является первым символом). Это делится IFS на просто '', так как нет символа '>'. Read затем назначает пустую строку обеим переменным. Второй вызов получает строку "тег> значение". Затем IFS разделяется на два поля: "тег" и "значение". Read затем присваивает переменные, такие как: ENTITY=tag
а также CONTENT=value
, Третий вызов получает строку '/tag>'. Это разделено IFS на два поля '/tag' и ''. Read затем присваивает переменные, такие как: ENTITY=/tag
а также CONTENT=
, Четвертый вызов вернет ненулевой статус, потому что мы достигли конца файла.
Теперь его цикл while немного очищен, чтобы соответствовать приведенному выше:
while read_dom; do
if [[ $ENTITY = "title" ]]; then
echo $CONTENT
exit
fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt
В первой строке просто сказано: "пока функция read_dom возвращает нулевой статус, сделайте следующее". Вторая строка проверяет, является ли объект, который мы только что видели, "заголовком". Следующая строка повторяет содержание тега. Четыре линии выходов. Если это не заголовок, цикл повторяется в шестой строке. Мы перенаправляем "xhtmlfile.xhtml" в стандартный ввод (для read_dom
функция) и перенаправить стандартный вывод в "titleOfXHTMLPage.txt" (эхо от ранее в цикле).
Теперь, учитывая следующее (похоже на то, что вы получаете, перечисляя ведро на S3) для input.xml
:
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Name>sth-items</Name>
<IsTruncated>false</IsTruncated>
<Contents>
<Key>item-apple-iso@2x.png</Key>
<LastModified>2011-07-25T22:23:04.000Z</LastModified>
<ETag>"0032a28286680abee71aed5d059c6a09"</ETag>
<Size>1785</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
</ListBucketResult>
и следующий цикл:
while read_dom; do
echo "$ENTITY => $CONTENT"
done < input.xml
Вы должны получить:
=>
ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/" =>
Name => sth-items
/Name =>
IsTruncated => false
/IsTruncated =>
Contents =>
Key => item-apple-iso@2x.png
/Key =>
LastModified => 2011-07-25T22:23:04.000Z
/LastModified =>
ETag => "0032a28286680abee71aed5d059c6a09"
/ETag =>
Size => 1785
/Size =>
StorageClass => STANDARD
/StorageClass =>
/Contents =>
Так что, если мы написали while
петля как у Юзема:
while read_dom; do
if [[ $ENTITY = "Key" ]] ; then
echo $CONTENT
fi
done < input.xml
Мы получили бы список всех файлов в корзине S3.
РЕДАКТИРОВАТЬ Если по какой-то причине local IFS=\>
не работает для вас, и вы устанавливаете его глобально, вы должны сбросить его в конце функции, например:
read_dom () {
ORIGINAL_IFS=$IFS
IFS=\>
read -d \< ENTITY CONTENT
IFS=$ORIGINAL_IFS
}
В противном случае любое разбиение строки, которое вы сделаете позже в скрипте, будет испорчено.
РЕДАКТИРОВАТЬ 2 Чтобы разделить пары имя / значение атрибута, вы можете увеличить read_dom()
вот так:
read_dom () {
local IFS=\>
read -d \< ENTITY CONTENT
local ret=$?
TAG_NAME=${ENTITY%% *}
ATTRIBUTES=${ENTITY#* }
return $ret
}
Затем напишите свою функцию для анализа и получите данные, которые вы хотите, вот так:
parse_dom () {
if [[ $TAG_NAME = "foo" ]] ; then
eval local $ATTRIBUTES
echo "foo size is: $size"
elif [[ $TAG_NAME = "bar" ]] ; then
eval local $ATTRIBUTES
echo "bar type is: $type"
fi
}
Тогда пока ты read_dom
вызов parse_dom
:
while read_dom; do
parse_dom
done
Затем приведен следующий пример разметки:
<example>
<bar size="bar_size" type="metal">bars content</bar>
<foo size="1789" type="unknown">foos content</foo>
</example>
Вы должны получить этот вывод:
$ cat example.xml | ./bash_xml.sh
bar type is: metal
foo size is: 1789
EDIT 3 другой пользователь сказал, что у него проблемы с ним во FreeBSD, и предложил сохранить состояние выхода из чтения и вернуть его в конце read_dom, например:
read_dom () {
local IFS=\>
read -d \< ENTITY CONTENT
local RET=$?
TAG_NAME=${ENTITY%% *}
ATTRIBUTES=${ENTITY#* }
return $RET
}
Я не вижу причин, почему это не должно работать
Инструменты командной строки, которые можно вызывать из сценариев оболочки, включают в себя:
- 4xpath - оболочка командной строки для пакета Python 4Suite
- XMLStarlet
- xpath - оболочка командной строки для библиотеки XPath в Perl
- Xidel - работает как с URL, так и с файлами. Также работает с JSON
Я также использую xmllint и xsltproc с небольшими сценариями XSL-преобразования для выполнения обработки XML из командной строки или в сценариях оболочки.
Вы можете сделать это очень легко, используя только bash. Вам нужно только добавить эту функцию:
rdom () { local IFS=\> ; read -d \< E C ;}
Теперь вы можете использовать rdom как read, но для html документов. При вызове rdom назначит элемент переменной E, а содержимое - переменной C.
Например, чтобы сделать то, что вы хотели сделать:
while rdom; do
if [[ $E = title ]]; then
echo $C
exit
fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt
Вы можете использовать утилиту xpath. Он устанавливается вместе с пакетом Perl XML-XPath.
Использование:
/usr/bin/xpath [filename] query
или XMLStarlet. Чтобы установить его на opensuse используйте:
sudo zypper install xmlstarlet
или попробуйте cnf xml
на других платформах.
Этого достаточно...
xpath xhtmlfile.xhtml '/html/head/title/text()' > titleOfXHTMLPage.txt
Проверьте XML2 с http://www.ofb.net/~egnor/xml2/ который преобразует XML в формат, ориентированный на строки.
Другой инструмент командной строки - мой новый Xidel. Он также поддерживает XPath 2 и XQuery, в отличие от уже упомянутого xpath/xmlstarlet.
Название может быть прочитано как:
xidel xhtmlfile.xhtml -e /html/head/title > titleOfXHTMLPage.txt
И у него также есть классная функция для экспорта нескольких переменных в bash. Например
eval $(xidel xhtmlfile.xhtml -e 'title := //title, imgcount := count(//img)' --output-format bash )
наборы $title
к названию и $imgcount
к количеству изображений в файле, которое должно быть таким же гибким, как и его синтаксический анализ непосредственно в bash.
Начиная с ответа Чада, вот ПОЛНОЕ рабочее решение для анализа UML, с более точной обработкой комментариев, всего с двумя небольшими функциями (более 2-х, но вы можете смешать их все). Я не говорю, что chad's один не работал вообще, но у него было слишком много проблем с плохо отформатированными XML-файлами: так что вам нужно быть немного хитрее, чтобы обрабатывать комментарии и неуместные пробелы /CR/TAB/ и т.д.
Цель этого ответа - предоставить готовые 2-функциональные функции bash для тех, кому требуется анализ UML без сложных инструментов, использующих Perl, Python или что-либо еще. Что касается меня, я не могу установить ни cpan, ни perl-модули для старой производственной ОС, над которой я работаю, и python недоступен.
Во-первых, определение UML-слов, используемых в этом посте:
<!-- comment... -->
<tag attribute="value">content...</tag>
РЕДАКТИРОВАТЬ: обновленные функции, с ручкой:
- Websphere xml (атрибуты xmi и xmlns)
- должен иметь совместимый терминал с 256 цветами
- 24 оттенка серого
- добавлена совместимость для IBM AIX bash 3.2.16(1)
Функции, во-первых, это xml_read_dom, который рекурсивно вызывается xml_read:
xml_read_dom() {
# https://stackru.com/questions/893585/how-to-parse-xml-in-bash
local ENTITY IFS=\>
if $ITSACOMMENT; then
read -d \< COMMENTS
COMMENTS="$(rtrim "${COMMENTS}")"
return 0
else
read -d \< ENTITY CONTENT
CR=$?
[ "x${ENTITY:0:1}x" == "x/x" ] && return 0
TAG_NAME=${ENTITY%%[[:space:]]*}
[ "x${TAG_NAME}x" == "x?xmlx" ] && TAG_NAME=xml
TAG_NAME=${TAG_NAME%%:*}
ATTRIBUTES=${ENTITY#*[[:space:]]}
ATTRIBUTES="${ATTRIBUTES//xmi:/}"
ATTRIBUTES="${ATTRIBUTES//xmlns:/}"
fi
# when comments sticks to !-- :
[ "x${TAG_NAME:0:3}x" == "x!--x" ] && COMMENTS="${TAG_NAME:3} ${ATTRIBUTES}" && ITSACOMMENT=true && return 0
# http://tldp.org/LDP/abs/html/string-manipulation.html
# INFO: oh wait it doesn't work on IBM AIX bash 3.2.16(1):
# [ "x${ATTRIBUTES:(-1):1}x" == "x/x" -o "x${ATTRIBUTES:(-1):1}x" == "x?x" ] && ATTRIBUTES="${ATTRIBUTES:0:(-1)}"
[ "x${ATTRIBUTES:${#ATTRIBUTES} -1:1}x" == "x/x" -o "x${ATTRIBUTES:${#ATTRIBUTES} -1:1}x" == "x?x" ] && ATTRIBUTES="${ATTRIBUTES:0:${#ATTRIBUTES} -1}"
return $CR
}
и второй:
xml_read() {
# https://stackru.com/questions/893585/how-to-parse-xml-in-bash
ITSACOMMENT=false
local MULTIPLE_ATTR LIGHT FORCE_PRINT XAPPLY XCOMMAND XATTRIBUTE GETCONTENT fileXml tag attributes attribute tag2print TAGPRINTED attribute2print XAPPLIED_COLOR PROSTPROCESS USAGE
local TMP LOG LOGG
LIGHT=false
FORCE_PRINT=false
XAPPLY=false
MULTIPLE_ATTR=false
XAPPLIED_COLOR=g
TAGPRINTED=false
GETCONTENT=false
PROSTPROCESS=cat
Debug=${Debug:-false}
TMP=/tmp/xml_read.$RANDOM
USAGE="${C}${FUNCNAME}${c} [-cdlp] [-x command <-a attribute>] <file.xml> [tag | \"any\"] [attributes .. | \"content\"]
${nn[2]} -c = NOCOLOR${END}
${nn[2]} -d = Debug${END}
${nn[2]} -l = LIGHT (no \"attribute=\" printed)${END}
${nn[2]} -p = FORCE PRINT (when no attributes given)${END}
${nn[2]} -x = apply a command on an attribute and print the result instead of the former value, in green color${END}
${nn[1]} (no attribute given will load their values into your shell; use '-p' to print them as well)${END}"
! (($#)) && echo2 "$USAGE" && return 99
(( $# < 2 )) && ERROR nbaram 2 0 && return 99
# getopts:
while getopts :cdlpx:a: _OPT 2>/dev/null
do
{
case ${_OPT} in
c) PROSTPROCESS="${DECOLORIZE}" ;;
d) local Debug=true ;;
l) LIGHT=true; XAPPLIED_COLOR=END ;;
p) FORCE_PRINT=true ;;
x) XAPPLY=true; XCOMMAND="${OPTARG}" ;;
a) XATTRIBUTE="${OPTARG}" ;;
*) _NOARGS="${_NOARGS}${_NOARGS+, }-${OPTARG}" ;;
esac
}
done
shift $((OPTIND - 1))
unset _OPT OPTARG OPTIND
[ "X${_NOARGS}" != "X" ] && ERROR param "${_NOARGS}" 0
fileXml=$1
tag=$2
(( $# > 2 )) && shift 2 && attributes=$*
(( $# > 1 )) && MULTIPLE_ATTR=true
[ -d "${fileXml}" -o ! -s "${fileXml}" ] && ERROR empty "${fileXml}" 0 && return 1
$XAPPLY && $MULTIPLE_ATTR && [ -z "${XATTRIBUTE}" ] && ERROR param "-x command " 0 && return 2
# nb attributes == 1 because $MULTIPLE_ATTR is false
[ "${attributes}" == "content" ] && GETCONTENT=true
while xml_read_dom; do
# (( CR != 0 )) && break
(( PIPESTATUS[1] != 0 )) && break
if $ITSACOMMENT; then
# oh wait it doesn't work on IBM AIX bash 3.2.16(1):
# if [ "x${COMMENTS:(-2):2}x" == "x--x" ]; then COMMENTS="${COMMENTS:0:(-2)}" && ITSACOMMENT=false
# elif [ "x${COMMENTS:(-3):3}x" == "x-->x" ]; then COMMENTS="${COMMENTS:0:(-3)}" && ITSACOMMENT=false
if [ "x${COMMENTS:${#COMMENTS} - 2:2}x" == "x--x" ]; then COMMENTS="${COMMENTS:0:${#COMMENTS} - 2}" && ITSACOMMENT=false
elif [ "x${COMMENTS:${#COMMENTS} - 3:3}x" == "x-->x" ]; then COMMENTS="${COMMENTS:0:${#COMMENTS} - 3}" && ITSACOMMENT=false
fi
$Debug && echo2 "${N}${COMMENTS}${END}"
elif test "${TAG_NAME}"; then
if [ "x${TAG_NAME}x" == "x${tag}x" -o "x${tag}x" == "xanyx" ]; then
if $GETCONTENT; then
CONTENT="$(trim "${CONTENT}")"
test ${CONTENT} && echo "${CONTENT}"
else
# eval local $ATTRIBUTES => eval test "\"\$${attribute}\"" will be true for matching attributes
eval local $ATTRIBUTES
$Debug && (echo2 "${m}${TAG_NAME}: ${M}$ATTRIBUTES${END}"; test ${CONTENT} && echo2 "${m}CONTENT=${M}$CONTENT${END}")
if test "${attributes}"; then
if $MULTIPLE_ATTR; then
# we don't print "tag: attr=x ..." for a tag passed as argument: it's usefull only for "any" tags so then we print the matching tags found
! $LIGHT && [ "x${tag}x" == "xanyx" ] && tag2print="${g6}${TAG_NAME}: "
for attribute in ${attributes}; do
! $LIGHT && attribute2print="${g10}${attribute}${g6}=${g14}"
if eval test "\"\$${attribute}\""; then
test "${tag2print}" && ${print} "${tag2print}"
TAGPRINTED=true; unset tag2print
if [ "$XAPPLY" == "true" -a "${attribute}" == "${XATTRIBUTE}" ]; then
eval ${print} "%s%s\ " "\${attribute2print}" "\${${XAPPLIED_COLOR}}\"\$(\$XCOMMAND \$${attribute})\"\${END}" && eval unset ${attribute}
else
eval ${print} "%s%s\ " "\${attribute2print}" "\"\$${attribute}\"" && eval unset ${attribute}
fi
fi
done
# this trick prints a CR only if attributes have been printed durint the loop:
$TAGPRINTED && ${print} "\n" && TAGPRINTED=false
else
if eval test "\"\$${attributes}\""; then
if $XAPPLY; then
eval echo "\${g}\$(\$XCOMMAND \$${attributes})" && eval unset ${attributes}
else
eval echo "\$${attributes}" && eval unset ${attributes}
fi
fi
fi
else
echo eval $ATTRIBUTES >>$TMP
fi
fi
fi
fi
unset CR TAG_NAME ATTRIBUTES CONTENT COMMENTS
done < "${fileXml}" | ${PROSTPROCESS}
# http://mywiki.wooledge.org/BashFAQ/024
# INFO: I set variables in a "while loop" that's in a pipeline. Why do they disappear? workaround:
if [ -s "$TMP" ]; then
$FORCE_PRINT && ! $LIGHT && cat $TMP
# $FORCE_PRINT && $LIGHT && perl -pe 's/[[:space:]].*?=/ /g' $TMP
$FORCE_PRINT && $LIGHT && sed -r 's/[^\"]*([\"][^\"]*[\"][,]?)[^\"]*/\1 /g' $TMP
. $TMP
rm -f $TMP
fi
unset ITSACOMMENT
}
и, наконец, функции rtrim, trim и echo2 (to stderr):
rtrim() {
local var=$@
var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters
echo -n "$var"
}
trim() {
local var=$@
var="${var#"${var%%[![:space:]]*}"}" # remove leading whitespace characters
var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters
echo -n "$var"
}
echo2() { echo -e "$@" 1>&2; }
Раскраска:
о, и вам понадобятся некоторые аккуратные цветовые динамические переменные, которые будут определены сначала и экспортированы тоже:
set -a
TERM=xterm-256color
case ${UNAME} in
AIX|SunOS)
M=$(${print} '\033[1;35m')
m=$(${print} '\033[0;35m')
END=$(${print} '\033[0m')
;;
*)
m=$(tput setaf 5)
M=$(tput setaf 13)
# END=$(tput sgr0) # issue on Linux: it can produces ^[(B instead of ^[[0m, more likely when using screenrc
END=$(${print} '\033[0m')
;;
esac
# 24 shades of grey:
for i in $(seq 0 23); do eval g$i="$(${print} \"\\033\[38\;5\;$((232 + i))m\")" ; done
# another way of having an array of 5 shades of grey:
declare -a colorNums=(238 240 243 248 254)
for num in 0 1 2 3 4; do nn[$num]=$(${print} "\033[38;5;${colorNums[$num]}m"); NN[$num]=$(${print} "\033[48;5;${colorNums[$num]}m"); done
# piped decolorization:
DECOLORIZE='eval sed "s,${END}\[[0-9;]*[m|K],,g"'
Как загрузить все эти вещи:
Либо вы знаете, как создавать функции и загружать их через FPATH (ksh), либо эмулировать FPATH (bash)
Если нет, просто скопируйте / вставьте все в командной строке.
Как это работает:
xml_read [-cdlp] [-x command <-a attribute>] <file.xml> [tag | "any"] [attributes .. | "content"]
-c = NOCOLOR
-d = Debug
-l = LIGHT (no \"attribute=\" printed)
-p = FORCE PRINT (when no attributes given)
-x = apply a command on an attribute and print the result instead of the former value, in green color
(no attribute given will load their values into your shell as $ATTRIBUTE=value; use '-p' to print them as well)
xml_read server.xml title content # print content between <title></title>
xml_read server.xml Connector port # print all port values from Connector tags
xml_read server.xml any port # print all port values from any tags
В режиме отладки (-d) комментарии и проанализированные атрибуты печатаются в stderr
yq можно использовать для разбора XML (требуемая версия для приведенных ниже примеров: >= 4.30.5).
Это легкий и портативный процессор YAML с командной строкой, который также может работать с XML. Синтаксис аналогичен jq.
Вход
<root>
<myel name="Foo" />
<myel name="Bar">
<mysubel>stairway to heaven</mysubel>
</myel>
</root>
Пример использования 1
yq --input-format xml '.root.myel.0.+@name' $FILE
Foo
Пример использования 2
имеет приятную встроенную функцию, позволяющую легко обрабатывать XML с помощью grep.
yq --input-format xml --output-format props $FILE
root.myel.0.+@name = Foo
root.myel.1.+@name = Bar
root.myel.1.mysubel = stairway to heaven
Пример использования 3
yq
также может преобразовывать ввод XML в JSON или YAML
yq --input-format xml --output-format json $FILE
{
"root": {
"myel": [
{
"+@name": "Foo"
},
{
"+@name": "Bar",
"mysubel": "stairway to heaven"
}
]
}
}
yq --input-format xml $FILE
(YAML
формат по умолчанию)
root:
myel:
- +@name: Foo
- +@name: Bar
mysubel: stairway to heaven
Я не знаю ни одного инструмента синтаксического анализа чистой оболочки XML. Поэтому вам, скорее всего, понадобится инструмент, написанный на другом языке.
Мой модуль Perl XML::Twig поставляется с таким инструментом: xml_grep
где вы, вероятно, напишите, что вы хотите, как xml_grep -t '/html/head/title' xhtmlfile.xhtml > titleOfXHTMLPage.txt
(-t
опция дает вам результат в виде текста вместо XML)
Ну, вы можете использовать утилиту xpath. Я думаю, что Perl XML::Xpath содержит его.
После некоторых исследований по переводу между форматами Linux и Windows путей к файлам в файлах XML я нашел интересные учебные пособия и решения по следующим вопросам:
- Общая информация о XPath
- Amara - коллекция инструментов Pythonic для XML
- Разработка Python/XML с 4Suite (2 части)
Хотя существует довольно много готовых консольных утилит, которые могут делать то, что вы хотите, вероятно, потребуется меньше времени, чтобы написать пару строк кода на языке программирования общего назначения, таком как Python, который вы можете легко расширить и адаптировать к твои нужды.
Вот скрипт Python, который использует lxml
для синтаксического анализа - он принимает имя файла или URL-адрес в качестве первого параметра, выражение XPath в качестве второго параметра и печатает строки / узлы, соответствующие данному выражению.
Пример 1
#!/usr/bin/env python
import sys
from lxml import etree
tree = etree.parse(sys.argv[1])
xpath_expression = sys.argv[2]
# a hack allowing to access the
# default namespace (if defined) via the 'p:' prefix
# E.g. given a default namespaces such as 'xmlns="http://maven.apache.org/POM/4.0.0"'
# an XPath of '//p:module' will return all the 'module' nodes
ns = tree.getroot().nsmap
if ns.keys() and None in ns:
ns['p'] = ns.pop(None)
# end of hack
for e in tree.xpath(xpath_expression, namespaces=ns):
if isinstance(e, str):
print(e)
else:
print(e.text and e.text.strip() or etree.tostring(e, pretty_print=True))
lxml
может быть установлен с pip install lxml
, На Ubuntu вы можете использовать sudo apt install python-lxml
,
использование
python xpath.py myfile.xml "//mynode"
lxml
также принимает URL в качестве входных данных:
python xpath.py http://www.feedforall.com/sample.xml "//link"
Примечание: если ваш XML имеет пространство имен по умолчанию без префикса (например,
xmlns=http://abc...
) тогда вы должны использоватьp
префикс (предоставляется 'hack') в ваших выражениях, например//p:module
чтобы получить модули отpom.xml
файл. В случае, еслиp
Префикс уже отображается в вашем XML, тогда вам нужно изменить скрипт, чтобы использовать другой префикс.
Пример 2
Одноразовый скрипт, который служит узкой цели извлечения имен модулей из файла apache maven. Обратите внимание, как имя узла (module
) имеет префикс пространства имен по умолчанию {http://maven.apache.org/POM/4.0.0}
:
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modules>
<module>cherries</module>
<module>bananas</module>
<module>pears</module>
</modules>
</project>
module_extractor.py:
from lxml import etree
for _, e in etree.iterparse(open("pom.xml"), tag="{http://maven.apache.org/POM/4.0.0}module"):
print(e.text)
Хотя кажется, что "никогда не анализируйте XML, JSON... из bash без надлежащего инструмента" - это разумный совет, я не согласен. Если это подработка, то поиск подходящего инструмента - это непростая задача, а затем изучить его... AWK может сделать это за считанные минуты. Мои программы должны работать со всеми вышеупомянутыми и многими другими типами данных. Черт, я не хочу тестировать 30 инструментов для анализа 5-7-10 различных форматов, которые мне нужны, если я могу решить проблему за считанные минуты. Меня не волнуют XML, JSON или что-то еще! Мне нужно одно решение для всех.
В качестве примера: моя программа SmartHome обслуживает наши дома. При этом он считывает огромное количество данных в слишком большом количестве различных форматов, которые я не могу контролировать. Я никогда не использую специальные инструменты, так как не хочу тратить больше минут на чтение необходимых мне данных. Это awk-решение с настройками FS и RS отлично работает с любым текстовым форматом. Но это может быть неправильный ответ, если ваша основная задача - работать в основном с большим количеством данных в этом формате!
С проблемой синтаксического анализа XML из bash я столкнулся вчера. Вот как я это делаю для любого иерархического формата данных. В качестве бонуса я назначаю данные непосредственно переменным в сценарии bash.
Чтобы легче было читать, я представлю решение поэтапно. Из тестовых данных OP я создал файл: test.xml
Разбор указанного XML в bash и извлечение данных в 90 символов:
awk 'BEGIN { FS="<|>"; RS="\n" }; /host|username|password|dbname/ { print $2, $4 }' test.xml
Обычно я использую более читаемую версию, так как в реальной жизни ее легче модифицировать, так как мне часто нужно тестировать иначе:
awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2,$4}' test.xml
Меня не волнует, как называется формат. Я ищу только самое простое решение. В этом конкретном случае я вижу из данных, что новая строка является разделителем записей (RS) и полями разделителей <> (FS). В моем исходном случае у меня была сложная индексация 6 значений в двух записях, связывая их, выясняя, когда данные существуют, плюс поля (записи) могут существовать или не существовать. Для идеального решения проблемы потребовалось 4 строчки awk. Итак, адаптируйте идею к каждой потребности, прежде чем использовать!
Вторая часть просто смотрит, что в строке есть требуемая строка (RS), и, если да, выводит необходимые поля (FS). Вышеупомянутое заняло у меня около 30 секунд, чтобы скопировать и адаптировать последнюю команду, которую я использовал таким образом (в 4 раза дольше). Вот и все! Сделано в 90 символов.
Но мне всегда нужно аккуратно преобразовывать данные в переменные в моем скрипте. Сначала я тестирую такие конструкции:
awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2"=\""$4"\"" }' test.xml
В некоторых случаях я использую printf вместо print. Когда я вижу, что все выглядит хорошо, я просто завершаю присвоение значений переменным. Я знаю, что многие думают, что eval - это "зло", не нужно комментировать:) Трюк отлично работает во всех четырех моих сетях в течение многих лет. Но продолжайте учиться, если не понимаете, почему это может быть плохой практикой! Включая назначения переменных bash и достаточный интервал, моему решению требуется 120 символов для всего.
eval $( awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2"=\""$4"\"" }' test.xml ); echo "host: $host, username: $username, password: $password dbname: $dbname"
Метод Юзема может быть улучшен путем изменения порядка <
а также >
знаки в rdom
функции и назначения переменных, так что:
rdom () { local IFS=\> ; read -d \< E C ;}
будет выглядеть так:
rdom () { local IFS=\< ; read -d \> C E ;}
Если синтаксический анализ выполняется не так, последний тег в XML-файле никогда не будет достигнут. Это может быть проблематично, если вы собираетесь вывести другой файл XML в конце while
петля.
Попробуй хпе. Он был построен специально для этой цели. Вы можете установить его с помощью python3 pip:
pip3 install xpe
Вы можете использовать его так:
curl example.com | xpe '//title'
Приведенная выше команда возвращает:
Пример домена
Это работает, если вы хотите атрибуты XML:
$ cat alfa.xml
<video server="asdf.com" stream="H264_400.mp4" cdn="limelight"/>
$ sed 's.[^ ]*..;s./>..' alfa.xml > alfa.sh
$ . ./alfa.sh
$ echo "$stream"
H264_400.mp4
Вступление
Большое спасибо за предыдущие ответы. Заголовок вопроса очень неоднозначный, так как вопросник спрашивает, как анализировать xml
когда вопросник на самом деле хочет разобрать xhtml
поговорим о двусмысленности. Хотя они похожи, они определенно не одинаковы. И с тех пор xml
а также xhtml
не то же самое, что было очень трудно найти решение, которое было бы именно тем, о чем просил вопросник. Однако я надеюсь, что решение, приведенное ниже, все же подойдет. Я хочу признать, что я не мог узнать, как искать специально для /html/head/title
, Теперь, когда об этом написано, я хочу сказать, что я не удовлетворен ранее ответами, так как некоторые из ответчиков без необходимости заново изобретают колесо, когда в анкете не говорилось, что скачивать пакет запрещено. Я вообще не понимаю ненужного кодирования. Я специально хочу повторить то, что человек в этой теме уже сказал: просто потому, что вы можете написать свой собственный парсер, не значит, что вы должны это делать - @Stephen Niedzielski. Относительно программирования: самый простой и кратчайший путь - это правило, которое предпочитают, никогда не делайте ничего более сложного, чем когда-либо необходимо. Решение было успешно протестировано на Windows 10 > Подсистема Windows для Linux> Ubuntu. Это возможно, если другой title
элемент будет существовать и будет выбран, это будет плохой результат, извините за такую возможность. Пример: если <body>
теги предшествуют <head>
теги и тому <body>
теги содержат <title>
тег, но это очень, очень маловероятно.
TLDR / Решение
На общем пути для решения, спасибо @Grisha, @Nat, Как разобрать XML в Bash?
При удалении тегов XML, спасибо @Johnsyweb, Как удалить теги XML из командной строки Unix?
1. Установите "пакет" xmlstarlet
2. Выполнить в Баш xmlstarlet sel -t -m "//_:title" -c . -n xhtmlfile.xhtml | head -1 | sed -e 's/<[^>]*>//g' > titleOfXHTMLPage.txt