В идеале я бы хотел:
cat xhtmlfile.xhtml |
getElementViaXPath --path='/html/head/title' |
sed -e 's%(^<title>|</title>$)%%g' > titleOfXHTMLPage.txt
В идеале я бы хотел:
cat xhtmlfile.xhtml |
getElementViaXPath --path='/html/head/title' |
sed -e 's%(^<title>|</title>$)%%g' > titleOfXHTMLPage.txt
На самом деле это просто объяснение ответа Юзема, но я не чувствовал, что такое большое редактирование должно быть сделано для кого-то еще, а комментарии не допускают форматирования, поэтому ...
rdom () { local IFS=\> ; read -d \< E C ;}
Назовем это "read_dom" вместо "rdom", немного отступим и будем использовать более длинные переменные:
read_dom () {
local IFS=\>
read -d \< ENTITY CONTENT
}
Итак, он определяет функцию с именем read_dom. Первая строка делает IFS (разделитель полей ввода) локальным для этой функции и меняет его на>. Это означает, что когда вы читаете данные вместо автоматического разделения на пробел, табуляцию или перевод строки, они разделяются на '>'. В следующей строке говорится о чтении ввода из stdin, и вместо остановки на новой строке остановитесь, когда вы увидите символ «‹ »(-d для флага разделителя). То, что читается, затем разделяется с помощью IFS и присваивается переменной ENTITY и CONTENT. Итак, возьмем следующее:
<tag>value</tag>
При первом вызове read_dom
получается пустая строка (поскольку «‹ »является первым символом). IFS разделяет это на просто "", поскольку нет символа ">". Затем чтение присваивает обеим переменным пустую строку. Второй вызов получает строку 'tag> value'. IFS разделяет это на два поля: «тег» и «значение». Затем чтение назначает такие переменные, как: ENTITY=tag
и CONTENT=value
. Третий вызов получает строку '/ tag>'. IFS разделяет это на два поля «/ tag» и «». Затем чтение присваивает такие переменные, как: ENTITY=/tag
и CONTENT=
. Четвертый вызов вернет ненулевой статус, потому что мы достигли конца файла.
Теперь его цикл while немного очищен, чтобы соответствовать приведенному выше:
while read_dom; do
if [[ $ENTITY = "title" ]]; then
echo $CONTENT
exit
fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt
В первой строке просто сказано: «пока функция read_dom возвращает нулевой статус, сделайте следующее». Вторая строка проверяет, является ли сущность, которую мы только что видели, "title". Следующая строка повторяет содержимое тега. Четыре линии выходов. Если это не объект заголовка, цикл повторяется на шестой строке. Мы перенаправляем «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>[email protected]</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 => [email protected]
/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
ИЗМЕНИТЬ 3 другой пользователь сказал, что у него проблемы с этим во FreeBSD и предложил сохранить статус выхода из чтения и вернуть его в конце read_dom, например:
read_dom () {
local IFS=\>
read -d \< ENTITY CONTENT
local RET=$?
TAG_NAME=${ENTITY%% *}
ATTRIBUTES=${ENTITY#* }
return $RET
}
Я не вижу причин, почему это не должно работать
num
: посмотрите на самый последний цикл while
в ответе Чада. Вместо echo $CONTENT
поставьте num=$CONTENT
.
- person ; 25.07.2012
read_dom
работает, только если я сделаю IFS глобальным: IFS='>'
. Пришлось удалить local
.
- person ; 25.07.2012
IFS=\< read ...
, который установит IFS только для вызова чтения. (Обратите внимание, что я никоим образом не поддерживаю практику использования read
для синтаксического анализа xml и считаю, что это чревато опасностями, и этого следует избегать.)
- person William Pursell; 27.11.2013
<cars><car><type>Volvo</type></car><car><type>Audio</type></car></cars>
Еще хуже, когда тебе нужен список всех «машин».
- person danger89; 04.01.2016
Вы можете сделать это очень легко, используя только 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
Инструменты командной строки, которые можно вызывать из сценариев оболочки, включают:
4xpath - оболочка командной строки для пакет 4Suite
xpath - оболочка командной строки для библиотеки Perl XPath
sudo apt-get install libxml-xpath-perl
Xidel - работает как с URL-адресами, так и с файлами. Также работает с JSON
Я также использую xmllint и xsltproc с небольшими сценариями преобразования XSL для обработки XML из командной строки или в сценариях оболочки.
Вы можете использовать утилиту xpath. Он устанавливается вместе с пакетом Perl XML-XPath.
Использование:
/usr/bin/xpath [filename] query
или XMLStarlet. Чтобы установить его в opensuse, используйте:
sudo zypper install xmlstarlet
или попробуйте cnf xml
на других платформах.
xpath
не подходит для использования в качестве компонента в сценариях. См., Например, stackoverflow.com/ questions / 15461737 / для уточнения.
- person tripleee; 27.07.2016
apt-get install xmlstarlet
- person rubo77; 24.12.2016
Этого достаточно ...
xpath xhtmlfile.xhtml '/html/head/title/text()' > titleOfXHTMLPage.txt
apt-get install libxml-xpath-perl
.
- person tres.14159; 18.01.2019
Проверьте XML2 на сайте http://www.ofb.net/~egnor/xml2/, который преобразует XML в строчно-ориентированный формат.
начиная с ответа Чада, вот ПОЛНОЕ рабочее решение для синтаксического анализа UML с правильной обработкой комментариев, всего с двумя небольшими функциями (более 2 бу, вы можете смешивать их все). Я не говорю, что у чада вообще не работает, но у него было слишком много проблем с плохо отформатированными XML-файлами: так что вам придется немного сложнее обрабатывать комментарии и неуместные пробелы / CR / TAB / и т. Д.
Цель этого ответа - предоставить готовые к использованию, готовые к использованию функции bash всем, кто нуждается в синтаксическом анализе UML без сложных инструментов с использованием perl, python или чего-либо еще. Что касается меня, я не могу установить ни cpan, ни модули perl для старой производственной ОС, над которой я работаю, а python недоступен.
Во-первых, определение слов UML, используемых в этом посте:
<!-- comment... -->
<tag attribute="value">content...</tag>
РЕДАКТИРОВАТЬ: обновленные функции с описанием:
xml_read_dom() {
# https://stackoverflow.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://stackoverflow.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 (для 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
./read_xml.sh: line 22: (-1): substring expression < 0
?
- person khmarbaise; 05.03.2014
[ "x${ATTRIBUTES:(-1):1}x" == "x?x" ] ...
- person khmarbaise; 05.03.2014
Я не знаю ни одного инструмента синтаксического анализа XML в чистом виде. Так что вам, скорее всего, понадобится инструмент, написанный на другом языке.
В моем модуле XML :: Twig Perl есть такой инструмент: xml_grep
, где вы, вероятно, написали бы то, что хотите, как xml_grep -t '/html/head/title' xhtmlfile.xhtml > titleOfXHTMLPage.txt
(опция -t
дает результат в виде текста вместо 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.
Что ж, вы можете использовать утилиту xpath. Я предполагаю, что Perl XML :: Xpath содержит его.
После некоторого исследования для перевода между форматами Linux и Windows путей к файлам в файлах XML я нашел интересные руководства и решения по:
Хотя существует довольно много готовых консольных утилит, которые могут делать то, что вы хотите, вероятно, потребуется меньше времени, чтобы написать пару строк кода на языке программирования общего назначения, таком как Python, который вы можете легко расширить и адаптировать к твои нужды.
Вот скрипт python, который использует lxml
для синтаксического анализа - он принимает имя файла или URL-адрес в качестве первого параметра. , выражение XPath в качестве второго параметра и выводит строки / узлы, соответствующие данному выражению.
#!/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, вам необходимо изменить скрипт, чтобы использовать другой префикс.
Одноразовый сценарий, который служит узкой цели извлечения имен модулей из файла 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)
pip install
вызов по apt-get
или yum
. Спасибо!
- person E. Moffat; 31.10.2018
Метод Юзема можно улучшить, изменив порядок знаков <
и >
в функции rdom
и присвоении переменных, чтобы:
rdom () { local IFS=\> ; read -d \< E C ;}
становится:
rdom () { local IFS=\< ; read -d \> C E ;}
Если синтаксический анализ не выполняется таким образом, последний тег в XML-файле никогда не достигается. Это может быть проблематично, если вы собираетесь вывести другой XML-файл в конце цикла while
.
Это работает, если вам нужны атрибуты 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, 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"
$(rm -rf ~)
до eval
этой команды (и если вы измените введенные кавычки с двойных на одинарные, их можно будет победить с помощью $(rm -rf ~)'$(rm -rf ~)'
).
- person Charles Duffy; 10.11.2020
'"'"'
- person Charles Duffy; 10.11.2020
eval "$(...)"
, а не только eval $(...)
. В качестве примера того, как последнее приводит к ошибочным результатам, попробуйте cmd=$'printf \'%s\\n\' \'first * line\''
, а затем сравните вывод eval $cmd
с выводом eval "$cmd"
- без кавычек ваш *
заменяется списком файлов в текущем каталоге до того, как eval
начнет свой синтаксический анализ (это означает, что сами имена файлов оцениваются как код, что открывает еще больше потенциальных возможностей для проблем с безопасностью).
- person Charles Duffy; 10.11.2020