В идеалния случай това, което бих искал да мога да направя, е:
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
Това наистина е просто обяснение на отговора на Yuzem, но не смятах, че толкова редактиране трябва да се прави на някой друг, а коментарите не позволяват форматиране, така че...
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 само на '', тъй като няма знак '>'. След това Read присвоява празен низ и на двете променливи. Второто извикване получава низа 'tag>value'. След това това се разделя от IFS на двете полета „tag“ и „value“. 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>[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:
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 и връщането му в края на 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 и съдържанието на var C.
Например, за да направите това, което искате да направите:
while rdom; do
if [[ $E = title ]]; then
echo $C
exit
fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt
Инструментите на командния ред, които могат да бъдат извикани от шел скриптове, включват:
xpath - обвивка на командния ред около XPath библиотеката на Perl
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 в редово-ориентиран формат.
като се започне от отговора на chad, тук е ПЪЛНОТО работещо решение за анализиране на UML, с правилно обработване на коментари, само с 2 малки функции (повече от 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. Предполагам, че XML::Xpath на perl го съдържа.
След известно проучване за превод между 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
(осигурен от „хака“) във вашите изрази, напр.//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
Методът на Yuzem може да бъде подобрен чрез обръщане на реда на знаците <
и >
във функцията 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 управлява нашите домове. Докато го прави, той чете множество данни в твърде много различни формати, които не мога да контролирам. Никога не използвам специални, подходящи инструменти, тъй като не искам да прекарвам повече от минути в четене на данните, от които се нуждая. С настройките на FS и RS, това awk решение работи перфектно за всеки текстов формат. Но може да не е правилният отговор, когато основната ви задача е да работите предимно с много данни в този формат!
Проблемът с анализирането на 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