Как да анализирам XML в Bash?

В идеалния случай това, което бих искал да мога да направя, е:

cat xhtmlfile.xhtml |
getElementViaXPath --path='/html/head/title' |
sed -e 's%(^<title>|</title>$)%%g' > titleOfXHTMLPage.txt

person Community    schedule 21.05.2009    source източник
comment
unix.stackexchange.com/questions/83385/ || superuser.com/questions/369996/   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 07.10.2015


Отговори (15)


Това наистина е просто обяснение на отговора на 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>&quot;0032a28286680abee71aed5d059c6a09&quot;</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 => &quot;0032a28286680abee71aed5d059c6a09&quot;
/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
}

Не виждам причина това да не работи

person chad    schedule 13.08.2011
comment
Списъкът е хубав, но наистина не знам къде да отида от там. Да кажем, че исках да сложа 1785, размера в променлива. Как бих направил това? - person obesechicken13; 23.07.2012
comment
@obesechicken13 Лесно, да кажем, че вашата променлива е с име num: вижте последния while цикъл в отговора на Чад. Вместо echo $CONTENT поставете num=$CONTENT. - person ; 25.07.2012
comment
За мен функцията read_dom работи само ако направя IFS глобален: IFS='>'. Трябваше да премахна local. - person ; 25.07.2012
comment
Ако направите IFS (разделителя на полето за въвеждане) глобален, трябва да го върнете обратно към първоначалната му стойност в края, редактирах отговора, за да има това. В противен случай всяко друго разделяне на вход, което правите по-късно във вашия скрипт, ще бъде объркано. Подозирам, че причината local да не работи за вас е, че или използвате bash в режим на съвместимост (като вашият shbang е #!/bin/sh), или това е стара версия на bash. - person chad; 25.07.2012
comment
@obesechicken13, добавих пример за анализ на атрибути. - person chad; 25.07.2012
comment
Само защото можете да напишете свой собствен анализатор, не означава, че трябва. - person Stephen Niedzielski; 24.04.2013
comment
@chad със сигурност казва нещо за работния процес/внедряването на AWS, че търсих отговор на bash xml, за да wget съдържанието на кофа S3! - person Alastair; 18.09.2013
comment
@Alastair Имам цял набор от S3 манипулационни bash скриптове, ще попитам моя мениджър дали мога да ги пусна. - person chad; 10.10.2013
comment
@Alastair вижте github.com/chad3814/s3scripts за набор от bash скриптове, които използваме за манипулиране на S3 обекти - person chad; 11.10.2013
comment
Принос на Grokkin, ето, @chad ! Проверете ги сега! - person Alastair; 16.10.2013
comment
Присвояването на IFS в локална променлива е крехко и не е необходимо. Просто направете: IFS=\< read ..., което ще зададе само IFS за извикването за четене. (Имайте предвид, че по никакъв начин не одобрявам практиката за използване на read за анализиране на xml и вярвам, че това е изпълнено с опасност и трябва да се избягва.) - person William Pursell; 27.11.2013
comment
Гласувах против за опит да пуснете свой собствен XML анализатор. Това е изключително лоша идея. - person Tomalak; 30.04.2015
comment
xml има вложена структура и можете да имате същите имена на „обекти“, като по този начин загубихте тази вложена структура. Което означава, че не можете да извлечете информацията, от която се нуждаете. Особено когато имената на обектите са еднакви, напр. <cars><car><type>Volvo</type></car><car><type>Audio</type></car></cars> Още по-лошо е, когато искате списък с всички „коли“. - person danger89; 04.01.2016
comment
Разборът на XML с grep и awk не е наред. Може да е приемлив компромис, ако XML-ите са достатъчно прости и нямате твърде много време, но никога не може да се нарече добро решение. - person peterh; 01.02.2018

Можете да направите това много лесно, като използвате само 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
person Yuzem    schedule 09.04.2010
comment
бихте ли разяснили това? Обзалагам се, че ви е напълно ясно.. и това може да е страхотен отговор - ако мога да кажа какво правите там.. можете ли да го разбиете малко по-подробно, евентуално генерирайки някакъв примерен резултат? - person Alex Gray; 04.07.2011
comment
Доверие на оригинала - този едноредов е толкова адски елегантен и невероятен. - person maverick; 06.12.2013
comment
страхотен хак, но трябваше да използвам двойни кавички като echo $C, за да предотвратя разширяването на обвивката и правилното тълкуване на крайните редове (зависи от кодирането) - person user311174; 16.01.2014
comment
Разборът на XML с grep и awk не е наред. Може да е приемлив компромис, ако XML-ите са достатъчно прости и нямате твърде много време, но никога не може да се нарече добро решение. - person peterh; 01.02.2018

Инструментите на командния ред, които могат да бъдат извикани от шел скриптове, включват:

  • 4xpath - обвивка на командния ред около 4Suite пакет

  • XMLStarlet

  • xpath - обвивка на командния ред около XPath библиотеката на Perl

    sudo apt-get install libxml-xpath-perl
    
  • Xidel – Работи както с URL адреси, така и с файлове. Работи и с JSON

Също така използвам xmllint и xsltproc с малки XSL преобразуващи скриптове, за да извършвам обработка на XML от командния ред или в шел скриптове.

person Nat    schedule 21.05.2009
comment
От къде мога да изтегля 'xpath' или '4xpath'? - person Opher; 15.04.2011
comment
да, второ гласуване/заявка - къде да изтеглите тези инструменти или искате да кажете, че трябва ръчно да напишете обвивка? Предпочитам да не губя време да го правя, освен ако не е необходимо. - person David; 22.11.2011
comment
sudo apt-get инсталирайте libxml-xpath-perl - person Andrew Wagner; 23.11.2012

Можете да използвате помощната програма xpath. Инсталира се с пакета Perl XML-XPath.

Употреба:

/usr/bin/xpath [filename] query

или XMLStarlet. За да го инсталирате на opensuse използвайте:

sudo zypper install xmlstarlet

или опитайте cnf xml на други платформи.

person Grisha    schedule 24.04.2012
comment
Използването на xml starlet определено е по-добър вариант от писането на собствен сериализатор (както е предложено в другите отговори). - person Bruno von Paris; 08.02.2013
comment
На много системи xpath, който идва предварително инсталиран, е неподходящ за използване като компонент в скриптове. Вижте напр. stackoverflow.com/ questions/15461737/ за уточнение. - person tripleee; 27.07.2016
comment
На Ubuntu/Debian apt-get install xmlstarlet - person rubo77; 24.12.2016

Това е достатъчно...

xpath xhtmlfile.xhtml '/html/head/title/text()' > titleOfXHTMLPage.txt
person teknopaul    schedule 05.01.2015
comment
В debian apt-get install libxml-xpath-perl . - person tres.14159; 18.01.2019

Вижте XML2 от http://www.ofb.net/~egnor/xml2/, който преобразува XML в редово-ориентиран формат.

person simon04    schedule 07.11.2009
comment
Много полезен инструмент. Връзката не работи (вижте https://web.archive.org/web/20160312110413/https://dan.egnor.name/xml2/ ), но има работещ, замразен клонинг в github: github.com/clone/xml2 - person Joshua Goldberg; 06.11.2020

като се започне от отговора на chad, тук е ПЪЛНОТО работещо решение за анализиране на UML, с правилно обработване на коментари, само с 2 малки функции (повече от 2, но можете да ги смесвате всички). Не казвам, че тази на Чад изобщо не работи, но имаше твърде много проблеми с лошо форматирани XML файлове: така че трябва да сте малко по-трудни, за да се справите с коментари и неправилно поставени интервали/CR/TAB/и т.н.

Целта на този отговор е да даде готови за употреба, готови за употреба 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://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

person scavenger    schedule 29.01.2014
comment
Опитвам се да използвам горните две функции, което произвежда следното: ./read_xml.sh: line 22: (-1): substring expression < 0? - person khmarbaise; 05.03.2014
comment
Ред 22: [ "x${ATTRIBUTES:(-1):1}x" == "x?x" ] ... - person khmarbaise; 05.03.2014
comment
съжалявам khmarbaise, това са функции на bash shell. Ако искате да ги адаптирате като shell скриптове, със сигурност трябва да очаквате някои малки адаптации! Също така актуализираните функции обработват вашите грешки;) - person scavenger; 08.04.2015

Не ми е известно да има инструмент за парсиране на XML с чиста обвивка. Така че най-вероятно ще ви трябва инструмент, написан на друг език.

Моят модул XML::Twig Perl идва с такъв инструмент: xml_grep, където вероятно ще напишете това, което искате като xml_grep -t '/html/head/title' xhtmlfile.xhtml > titleOfXHTMLPage.txt (опцията -t ви дава резултата като текст вместо xml)

person mirod    schedule 21.05.2009

Друг инструмент за команден ред е моят нов 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.

person BeniBela    schedule 27.03.2013

Е, можете да използвате помощната програма xpath. Предполагам, че XML::Xpath на perl го съдържа.

person alamar    schedule 21.05.2009

След известно проучване за превод между Linux и Windows формати на файловите пътища в XML файлове, намерих интересни уроци и решения за:

person user485380    schedule 24.10.2010

Въпреки че има доста готови конзолни помощни програми, които могат да направят това, което искате, вероятно ще ви отнеме по-малко време да напишете няколко реда код на език за програмиране с общо предназначение като 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 (осигурен от „хака“) във вашите изрази, напр. //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)
person ccpizza    schedule 25.10.2017
comment
Това е страхотно, когато или искате да избегнете инсталирането на допълнителни пакети, или нямате достъп до тях. На компилираща машина мога да оправдая допълнително 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 цикъла.

person michaelmeyer    schedule 24.01.2013

Това работи, ако искате 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
person Steven Penny    schedule 16.06.2012

Въпреки че изглежда, че „никога не анализирайте 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"
person Pila    schedule 09.05.2020
comment
Има сериозни проблеми със сигурността при този подход. Не искате парола, съдържаща $(rm -rf ~) до eval тази команда (и ако сте променили вашите инжектирани кавички от двойни на единични, те могат да бъдат победени с $(rm -rf ~)'$(rm -rf ~)'). - person Charles Duffy; 10.11.2020
comment
...така че, ако искате да направите това безопасно, трябва да и двете (1) превключите от инжектиране на двойни кавички към единични кавички; и (2) заменете всички буквални единични кавички в данните с конструкция като '"'"' - person Charles Duffy; 10.11.2020
comment
Също така, eval "$(...)", не само eval $(...). За пример как последното води до резултати с грешки, опитайте cmd=$'printf \'%s\\n\' \'first * line\'' и след това сравнете изхода на eval $cmd с изхода на eval "$cmd" -- без кавичките вашият * се заменя със списък от файлове в текущата директория, преди eval да започне своята анализиране (което означава, че самите имена на файлове се оценяват като код, отваряйки още повече потенциално място за проблеми със сигурността). - person Charles Duffy; 10.11.2020