В идеале, я хотел бы иметь возможность:
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
Это действительно просто объяснение ответа
read_dom () {
local IFS=\>
read -d \< ENTITY CONTENT
}
<tag>value</tag>
Теперь его цикл while очистился немного, чтобы соответствовать выше:
while read_dom; do
if [[ $ENTITY = "title" ]]; then
echo $CONTENT
exit
fi
done < xhtmlfile.xhtml > 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
, такой как Yuzem's:
while read_dom; do
if [[ $ENTITY = "Key" ]] ; then
echo $CONTENT
fi
done < input.xml
Мы получим список всех файлов в ведре S3.
read_dom () {
ORIGINAL_IFS=$IFS
IFS=\>
read -d \< ENTITY CONTENT
IFS=$ORIGINAL_IFS
}
В противном случае любое разделение строк, которое вы сделаете позже в script, будет испорчено.
РЕДАКТИРОВАТЬ 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
read_dom () {
local IFS=\>
read -d \< ENTITY CONTENT
local ret=$?
TAG_NAME=${ENTITY%% *}
ATTRIBUTES=${ENTITY#* }
return $ret
}
Я не вижу причин, почему это не должно работать
Вы можете сделать это очень легко, используя только bash. Вам нужно только добавить эту функцию:
rdom () { local IFS=\> ; read -d \< E C ;}
Теперь вы можете использовать rdom, например read, но для html-документов. Когда вызываемый rdom присваивает элемент переменной E и содержимому var C.
Например, чтобы сделать то, что вы хотели сделать:
while rdom; do
if [[ $E = title ]]; then
echo $C
exit
fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt
Средства командной строки, которые могут быть вызваны из сценариев оболочки, включают в себя:
Я также использую xmllint и xsltproc с небольшими сценариями преобразования XSL для обработки XML из командной строки или в сценариях оболочки.
Вы можете использовать утилиту xpath. Он установлен с пакетом Perl XML-XPath.
Использование:
/usr/bin/xpath [filename] query
или XMLStarlet. Чтобы установить его на openuse, используйте:
sudo zypper install xmlstarlet
или попробуйте cnf xml
на других платформах.
Отметьте 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.
Этого достаточно...
xpath xhtmlfile.xhtml '/html/head/title/text()' > titleOfXHTMLPage.txt
Мне не известно о любом чистом XML-синтаксическом инструменте. Поэтому вам, скорее всего, понадобится инструмент, написанный на другом языке.
Мой модуль XML:: Twig Perl поставляется с таким инструментом: xml_grep
, где вы, вероятно, напишите, что хотите, как xml_grep -t '/html/head/title' xhtmlfile.xhtml > titleOfXHTMLPage.txt
(опция -t
дает результат как текст вместо xml)
Ну, вы можете использовать утилиту xpath. Я предполагаю, что perl XML:: Xpath содержит его.
После некоторых исследований для перевода между форматами файлов Linux и Windows в файлах XML я нашел интересные руководства и решения на:
Это работает, если вам нужны атрибуты 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
Метод Yuzem может быть улучшен путем обращения порядка знаков <
и >
в функции rdom
и назначения переменных, так что:
rdom () { local IFS=\> ; read -d \< E C ;}
становится:
rdom () { local IFS=\< ; read -d \> C E ;}
Если разбор не выполняется так, последний тег в XML файле никогда не будет достигнут. Это может быть проблематично, если вы собираетесь вывести другой XML файл в конце цикла while
.
начиная с ответа chad, вот работающее решение COMPLETE для анализа UML, с обработкой комментариями с помощью propper, всего лишь с двумя небольшими функциями (более 2 бу, вы можете их смешивать). Я не говорю, что chad не работал вообще, но у него было слишком много проблем с плохо сформированными файлами XML: так что вам нужно быть более сложным для обработки комментариев и неулокальных мест/CR/TAB/и т.д.
Цель этого ответа - предоставить готовые функции "2-го использования" из bash для всех, нуждающихся в разборе UML без сложных инструментов, используя perl, python или что-то еще. Что касается меня, я не могу установить cpan и perl-модули для старой операционной системы, над которой я работаю, и python недоступен.
Во-первых, определение слов UML, используемых в этом сообщении:
<!-- comment... -->
<tag attribute="value">content...</tag>
EDIT: обновленные функции, с дескриптором:
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 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 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 [email protected]
var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters
echo -n "$var"
}
trim() {
local [email protected]
var="${var#"${var%%[![:space:]]*}"}" # remove leading whitespace characters
var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters
echo -n "$var"
}
echo2() { echo -e "[email protected]" 1>&2; }
oh, и вам понадобятся некоторые опрятные раскрашивающие динамические переменные, которые будут сначала определены и экспортированы:
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 > ] [tag | "any" ] [attributes.. | "Содержание" ]
-c = NOCOLOR
-d = Отладка
-l = LIGHT (no\ "attribute = \" напечатан)
-p = FORCE PRINT (если атрибуты не указаны)
-x = применить команду к атрибуту и напечатать результат вместо прежнего значения в зеленом цвете
(никакие атрибуты не будут загружать их значения в вашу оболочку как $ATTRIBUTE = значение; используйте '-p' для их печати)
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
Хотя существует немало готовых консольных утилит, которые могут делать то, что вам нужно, по моему опыту требуется значительно меньше времени для написания пары строк кода на языке программирования общего назначения, таком как Python, который вы можете легко расширяться и адаптироваться к вашим потребностям.
Вот пример python script с lxml
- он принимает имя файла в качестве первого параметр, выражение XPath как второй параметр и печатает строки/узлы, соответствующие данному выражению XPath.
#!/usr/bin/env python
import sys
from lxml import etree
tree = etree.parse(sys.argv[1])
xpath_expression = sys.argv[2]
for e in tree.xpath(xpath_expression):
if isinstance(e, str):
print(e)
else:
print(e.text or etree.tostring(e))
lxml
можно установить с помощью pip install lxml
. На ubuntu вы можете использовать sudo apt install python-lxml
.
python xmlcat.py file.xml "//mynode"
lxml
также может принимать URL-адрес как вход:
python xmlcat.py http://example.com/file.xml "//mynode"
Извлеките атрибут url
в enclosure
node (т.е. <enclosure url="http:...""..>
):
python xmlcat.py xmlcat.py file.xml "//enclosure/@url"
Примечание:
lxml
отлично работает с точки зрения скорости, так как это хорошо оптимизированный собственный модуль, написанный на C.
2 ¢: Не тратьте время на изучение неясного синтаксиса некоторой наполовину поддерживаемой утилиты. Жизнь слишком коротка для этого. Вместо этого используйте доступные ресурсы, чтобы расширить свои навыки программирования с помощью широко используемых API.