Як розібрати XML у Bash?


Відповіді:


153

Це справді лише пояснення відповіді Юзема , але я не відчував, що таке редагування потрібно робити комусь іншому, а коментарі не дозволяють форматувати, так що ...

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 на просто "", оскільки немає ">" символу. Прочитати потім призначає порожній рядок обом змінним. Другий виклик отримує рядок "тег> значення". Це розділяється 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повертає нульовий статус, зробіть наступне". Другий рядок перевіряє, чи сутність, яку ми щойно бачили, - "назва". Наступний рядок перегукується з вмістом тегу. Чотири лінії виходять. Якщо це не сутність заголовка, то цикл повторюється в шостому рядку. Ми перенаправляємо "xhtmlfile.xhtml" на стандартний вхід (для read_domфункції) і перенаправляємо стандартний висновок на "titleOfXHTMLPage.txt" (відлуння від попереднього циклу).

Тепер надано наступне (подібне до того, що ви отримуєте з перерахування відра на S3) для input.xml:

<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Name>sth-items</Name>
  <IsTruncated>false</IsTruncated>
  <Contents>
    <Key>item-apple-iso@2x.png</Key>
    <LastModified>2011-07-25T22:23:04.000Z</LastModified>
    <ETag>&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 => item-apple-iso@2x.png
/Key => 
LastModified => 2011-07-25T22:23:04.000Z
/LastModified => 
ETag => &quot;0032a28286680abee71aed5d059c6a09&quot;
/ETag => 
Size => 1785
/Size => 
StorageClass => STANDARD
/StorageClass => 
/Contents => 

Отже, якщо ми написали whileцикл, як Юзем:

while read_dom; do
    if [[ $ENTITY = "Key" ]] ; then
        echo $CONTENT
    fi
done < input.xml

Ми отримаємо список усіх файлів у відрі S3.

EDIT Якщо з якихось причин local IFS=\>не працює для вас, і ви встановили його в усьому світі, вам слід скинути його в кінці функції, наприклад:

read_dom () {
    ORIGINAL_IFS=$IFS
    IFS=\>
    read -d \< ENTITY CONTENT
    IFS=$ORIGINAL_IFS
}

В іншому випадку будь-який розбиття рядка, який ви робите пізніше в сценарії, буде зіпсований.

EDIT 2 Для розділення пар імен атрибутів / значень можна read_dom()додати подібне:

read_dom () {
    local IFS=\>
    read -d \< ENTITY CONTENT
    local ret=$?
    TAG_NAME=${ENTITY%% *}
    ATTRIBUTES=${ENTITY#* }
    return $ret
}

Потім напишіть свою функцію для розбору та отримання потрібних даних:

parse_dom () {
    if [[ $TAG_NAME = "foo" ]] ; then
        eval local $ATTRIBUTES
        echo "foo size is: $size"
    elif [[ $TAG_NAME = "bar" ]] ; then
        eval local $ATTRIBUTES
        echo "bar type is: $type"
    fi
}

Потім, поки ви read_domтелефонуєте parse_dom:

while read_dom; do
    parse_dom
done

Тоді наведено наступний приклад розмітки:

<example>
  <bar size="bar_size" type="metal">bars content</bar>
  <foo size="1789" type="unknown">foos content</foo>
</example>

Ви повинні отримати цей вихід:

$ cat example.xml | ./bash_xml.sh 
bar type is: metal
foo size is: 1789

Інший користувач EDIT 3 заявив, що у FreeBSD виникають проблеми з ним, і запропонував зберегти статус виходу з прочитаного та повернути його в кінці read_dom, наприклад:

read_dom () {
    local IFS=\>
    read -d \< ENTITY CONTENT
    local RET=$?
    TAG_NAME=${ENTITY%% *}
    ATTRIBUTES=${ENTITY#* }
    return $RET
}

Я не бачу жодної причини, чому це не повинно працювати


2
Якщо ви зробите IFS (роздільник поля введення) глобальним, вам слід повернути його до початкового значення в кінці, я змінив відповідь, щоб це було. Інакше будь-яке інше розбиття входу, яке ви зробите пізніше у вашому сценарії, буде зірвано. Я підозрюю, що локальна робота для вас не працює в тому, що або ви використовуєте bash в режимі сумісності (наприклад, ваш shbang є #! / Bin / sh), або це стародавня версія bash.
чад

30
Тільки тому, що ви можете написати власний парсер, не означає, що слід.
Stephen Niedzielski

1
@chad це, безумовно, говорить щось про робочий процес / впровадження AWS, що я шукав відповідь на "bash xml", щоб також вивірити вміст відра S3!
Аластер

2
@Alastair див. Github.com/chad3814/s3scripts для набору скриптів bash, які ми використовуємо для маніпулювання об'єктами S3
Чад

5
Призначення IFS в локальній змінній є крихким і не є необхідним. Просто зробіть:, IFS=\< read ...який встановить IFS лише для дзвінка зчитування. (Зауважте, що я жодним чином не схвалюю практику використання readдля розбору xml, і я вважаю, що це загрожує небезпекою і цього слід уникати.)
Вільям Перселл

64

Це можна зробити дуже легко, використовуючи лише баш. Вам потрібно лише додати цю функцію:

rdom () { local IFS=\> ; read -d \< E C ;}

Тепер ви можете використовувати rdom як для читання, але для html-документів. При виклику rdom призначить елемент змінній E, а вміст - var C.

Наприклад, щоб зробити те, що ви хотіли зробити:

while rdom; do
    if [[ $E = title ]]; then
        echo $C
        exit
    fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt

Ви могли б детальніше розглянути це? Я б сказав, що це вам абсолютно зрозуміло .. і це може бути чудовою відповіддю - якщо я можу сказати, що ви там робите .. чи можете ви розбити це ще трохи, можливо, генеруючи деякий вибірки?
Алекс Грей

1
Орієнтований на оригінал - цей однолінійний настільки чудовий елегантний та дивовижний.
maverick

1
чудовий хак, але мені довелося використовувати подвійні лапки на зразок відлуння "$ C" для запобігання розширення оболонки та правильної інтерпретації кінцевих рядків (залежить від обкладинки)
user311174

8
Розбір XML з grep and awk - це не нормально . Це може бути прийнятним компромісом, якщо XML досить прості і у вас не так багато часу, але це не можна назвати хорошим рішенням ніколи.
peterh

59

Інструменти командного рядка, які можна викликати із скриптів оболонки, включають:

  • 4xpath - обгортка командного рядка навколо пакету 4Suite Python
  • XMLStarlet
  • xpath - обгортка командного рядка навколо бібліотеки XPath Perl
  • Xidel - працює з URL-адресами, а також файлами. Також працює з JSON

Я також використовую xmllint і xsltproc з невеликими сценаріями перетворення XSL для обробки XML з командного рядка або в скриптах оболонки.


2
Де я можу завантажити "xpath" або "4xpath" з?
Опер

3
так, друге голосування / запит - де завантажити ці інструменти, чи ви маєте на увазі, що потрібно вручну написати обгортку? Я б краще не витрачав час на це, якщо не потрібно.
Девід

4
sudo apt-get install libxml-xpath-perl
Andrew Wagner

22

Ви можете використовувати утиліту xpath. Він встановлений разом з пакетом Perl XML-XPath.

Використання:

/usr/bin/xpath [filename] query

або XMLStarlet . Щоб встановити його на opensuse, використовуйте:

sudo zypper install xmlstarlet

або спробуйте cnf xmlна інших платформах.


5
Використання xml starlet, безумовно, кращий варіант, ніж написання власного серіалізатора (як пропонується в інших відповідях).
Бруно фон Париж

У багатьох системах, xpathякі попередньо встановлені, непридатні для використання в якості компонента в сценаріях. Дивіться, наприклад, stackoverflow.com/questions/15461737/… для розробки.
трійка

2
У Ubuntu / Debianapt-get install xmlstarlet
rubo77



5

Починаючи з відповіді чаду, ось ПОСЛУГ робоче рішення для розбору UML, з пропперною обробкою коментарів, лише з двома маленькими функціями (більше 2 бут ви можете їх змішати всі). Я не можу сказати, що один з Chad взагалі не працював, але у нього виникло занадто багато проблем із неправильно сформованими XML-файлами: тому вам потрібно бути трохи складніше, щоб обробляти коментарі та неправильно розміщені пробіли / CR / TAB / тощо.

Метою цієї відповіді є надання готових 2-х функцій для використання програм, що не потребують аналізу, для тих, хто потребує розбору UML без складних інструментів, використовуючи perl, python або щось інше. Щодо мене, я не можу встановити ні cpan, ні perl модулі для старої виробничої ОС, над якою працюю, і python недоступний.

По-перше, визначення слів UML, використаних у цій публікації:

<!-- comment... -->
<tag attribute="value">content...</tag>

EDIT: оновлені функції, з ручкою:

  • Websphere xml (атрибути xmi та xmlns)
  • повинен мати сумісний термінал з 256 кольорами
  • 24 відтінки сірого
  • додано сумісність для IBM AIX bash 3.2.16 (1)

Функції, по-перше, це xml_read_dom, який викликається рекурсивно xml_read:

xml_read_dom() {
# /programming/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() {
# /programming/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?
khmarbaise

22 рядок:[ "x${ATTRIBUTES:(-1):1}x" == "x?x" ] ...
hhmabaise

Вибачте, khmarbaise, це функції оболонки bash. Якщо ви хочете адаптувати їх як сценарії оболонки, вам неодмінно слід очікувати незначних адаптацій! Також оновлені функції обробляють ваші помилки;)
scavenger

4

Я не знаю жодного інструмента розбору XML чистих оболонок XML. Тож вам, швидше за все, знадобиться інструмент, написаний іншою мовою.

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


4

Іншим інструментом командного рядка є мій новий 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.


Це саме те, що мені було потрібно! :)
Томас Даугаард

2

Ну, ви можете використовувати утиліту xpath. Я думаю, що XML :: Xpath Perl містить його.


2

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


2

Хоча існує досить багато готових утиліт консолі, які можуть робити те, що ви хочете, можливо, знадобиться менше часу, щоб написати пару рядків коду на загальній мові програмування, наприклад, Python, до якого ви можете легко розширити і адаптувати ваші потреби.

Ось сценарій python, який використовується lxmlдля синтаксичного аналізу - він приймає ім'я файлу чи URL-адреси в якості першого параметра, вираз XPath як другий параметр, і друкує рядки / вузли, що відповідають заданому виразу.

Приклад 1

#!/usr/bin/env python
import sys
from lxml import etree

tree = etree.parse(sys.argv[1])
xpath_expression = sys.argv[2]

#  a hack allowing to access the
#  default namespace (if defined) via the 'p:' prefix    
#  E.g. given a default namespaces such as 'xmlns="http://maven.apache.org/POM/4.0.0"'
#  an XPath of '//p:module' will return all the 'module' nodes
ns = tree.getroot().nsmap
if ns.keys() and None in ns:
    ns['p'] = ns.pop(None)
#   end of hack    

for e in tree.xpath(xpath_expression, namespaces=ns):
    if isinstance(e, str):
        print(e)
    else:
        print(e.text and e.text.strip() or etree.tostring(e, pretty_print=True))

lxmlможна встановити за допомогою pip install lxml. На ubuntu можна використовувати sudo apt install python-lxml.

Використання

python xpath.py myfile.xml "//mynode"

lxml також приймає URL як вхідний:

python xpath.py http://www.feedforall.com/sample.xml "//link"

Примітка : Якщо у вашому XML є простір імен за замовчуванням без префікса (наприклад xmlns=http://abc...), тоді ви повинні використовувати pв своїх виразах префікс (наданий 'hack'), наприклад //p:moduleдля отримання модулів з pom.xmlфайлу. Якщо pпрефікс вже відображено у вашому XML, вам потрібно буде змінити сценарій, щоб використовувати інший префікс.


Приклад 2

Одноразовий сценарій, який виконує вузьку мету вилучення імен модулів з файлу apache maven. Зауважте, як ім'я вузла ( module) встановлено з простором імен за замовчуванням {http://maven.apache.org/POM/4.0.0}:

pom.xml :

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modules>
        <module>cherries</module>
        <module>bananas</module>
        <module>pears</module>
    </modules>
</project>

module_extractor.py :

from lxml import etree
for _, e in etree.iterparse(open("pom.xml"), tag="{http://maven.apache.org/POM/4.0.0}module"):
    print(e.text)

Це приголомшливо, коли ви хочете уникати встановлення додаткових пакетів або не маєте доступу до них. На будівельному машині, я можу виправдати додатковий за pip installкадром apt-getабо yumвиклику. Дякую!
Е. Моффат

0

Метод Юзема можна вдосконалити, перевернувши порядок знаків <та >знаків у rdomфункції та призначення змінних, щоб:

rdom () { local IFS=\> ; read -d \< E C ;}

стає:

rdom () { local IFS=\< ; read -d \> C E ;}

Якщо розбір не виконаний так, останній тег у XML-файлі ніколи не досягається. Це може бути проблематично, якщо ви плануєте вивести ще один XML-файл у кінці whileциклу.


0

Це працює, якщо ви бажаєте атрибутів 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

-1

Хоча здається, що "ніколи не розбирайте XML, JSON ... з баш без належного інструменту" - це слушна порада, я не згоден. Якщо це побічна робота, слід шукати належний інструмент, а потім навчитися ... 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"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.