Как разобрать 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 для deliminator). То, что читается, затем разделяется с помощью IFS и присваивается переменной ENTITY и CONTENT. Итак, возьмите следующее:

<tag>value</tag>

Первый вызов, чтобы read_domполучить пустую строку (так как «<» является первым символом). Это делится IFS на просто '', так как нет символа '>'. Read затем назначает пустую строку обеим переменным. Второй вызов получает строку «тег> значение». Затем IFS разделяется на два поля: «тег» и «значение». 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>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.

РЕДАКТИРОВАТЬ Если по какой-то причине у 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

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

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

Я не вижу причин, почему это не должно работать

Чад
источник
2
Если вы сделаете IFS (разделитель входных полей) глобальным, вы должны в конце сбросить его на исходное значение, я отредактировал ответ, чтобы получить его. В противном случае любое другое разбиение ввода, которое вы сделаете позже в вашем скрипте, будет испорчено. Я подозреваю, что local не работает для вас, потому что вы используете bash в режиме совместимости (например, ваш shbang #! / Bin / sh) или это древняя версия bash.
Чад
30
То, что вы можете написать свой собственный парсер, не означает, что вы должны это делать.
Стивен Недзельски
1
@chad это , безусловно , говорит о AWS»рабочего процесса / реализации , что я искал ответ на„Баш XML“также WGET содержимого ведра S3!
Аластер
2
@Alastair см github.com/chad3814/s3scripts для набора Баш сценариев , которые мы используем , чтобы манипулировать объектами S3
чадь
5
Назначение IFS в локальной переменной хрупко и не обязательно. Просто сделайте:, IFS=\< read ...который будет устанавливать IFS только для вызова чтения. (Обратите внимание, что я никоим образом не одобряю практику использования readдля анализа xml, и я считаю, что это чревато опасностью и этого следует избегать.)
Уильям Перселл,
64

Вы можете сделать это очень легко, используя только bash. Вам нужно только добавить эту функцию:

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

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

Например, чтобы сделать то, что вы хотели сделать:

while rdom; do
    if [[ $E = title ]]; then
        echo $C
        exit
    fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt
Yuzem
источник
не могли бы вы уточнить это? Могу поспорить, что это совершенно ясно для вас ... и это могло бы быть отличным ответом - если бы я мог рассказать, что вы там делали ... вы можете немного разбить его, возможно, генерируя какой-нибудь пример выходных данных?
Алекс Грей
1
Cred to original - этот однострочный, чертовски элегантный и удивительный.
Индивидуалист
1
отличный хак, но мне пришлось использовать двойные кавычки, такие как echo "$ C", чтобы предотвратить расширение оболочки и правильную интерпретацию конечных строк (зависит от конца)
user311174
8
С парсингом XML с помощью grep и awk не все в порядке . Это может быть приемлемым компромиссом, если XML-файлы достаточно просты и у вас не так много времени, но это никогда нельзя назвать хорошим решением.
Петер - Восстановить Монику
59

Инструменты командной строки, которые можно вызывать из сценариев оболочки, включают в себя:

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

Я также использую xmllint и xsltproc с небольшими сценариями XSL-преобразования для выполнения обработки XML из командной строки или в сценариях оболочки.

натуральный
источник
2
Где я могу скачать 'xpath' или '4xpath'?
Офер
3
да, второй голос / запрос - где скачать эти инструменты, или вы имеете в виду, что нужно вручную написать обертку? Я бы предпочел не тратить время на это, если в этом нет необходимости.
Дэвид
4
sudo apt-get install libxml-xpath-perl
Эндрю Вагнер,
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/… для уточнения.
tripleee
2
В Ubuntu / Debianapt-get install xmlstarlet
rubo77
12

Этого достаточно ...

xpath xhtmlfile.xhtml '/html/head/title/text()' > titleOfXHTMLPage.txt
teknopaul
источник
Спасибо, быстро и сделал работу для меня
Мигель Мота
1
В дебиане apt-get install libxml-xpath-perl.
tres.14159
работает как шарм
Александру-Михай Манолеску
5

Исходя из ответа Чада, вот ПОЛНОЕ рабочее решение для анализа UML, с более точной обработкой комментариев, всего с двумя небольшими функциями (более 2-х, но вы можете смешать их все). Я не говорю, что chad's один не работал вообще, но у него было слишком много проблем с плохо отформатированными XML-файлами: так что вам нужно быть немного хитрее, чтобы обрабатывать комментарии и неуместные пробелы / CR / TAB / и т.д.

Цель этого ответа - предоставить готовые 2-функциональные функции 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() {
# /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 (to 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" ] ...
khmarbaise
извините, хмарбаисе, это функции оболочки bash. Если вы хотите адаптировать их как сценарии оболочки, вам, безусловно, следует ожидать некоторых незначительных изменений! Также обновленные функции обрабатывают ваши ошибки;)
scavenger
4

Я не знаю ни одного чистого инструмента синтаксического анализа XML оболочки. Поэтому вам, скорее всего, понадобится инструмент, написанный на другом языке.

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

mirod
источник
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.

BeniBela
источник
Это именно то, что мне было нужно! :)
Томас Даугор
2

Ну, вы можете использовать утилиту xpath. Я думаю, что Perl XML :: Xpath содержит его.

Аламар
источник
2

После некоторых исследований по переводу между форматами Linux и Windows путей к файлам в XML-файлах я нашел интересные учебные пособия и решения по следующим вопросам:

user485380
источник
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)
ccpizza
источник
Это замечательно, когда вы либо хотите избежать установки дополнительных пакетов, либо не имеете доступа к ним. На строительной машине, я могу оправдать дополнительный за pip installкадром apt-getили yumвызова. Спасибо!
Э. Моффат
0

Метод Yuzem может быть улучшен путем инвертирования порядка <и >знаков в rdomфункции и переменных задания, так что:

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

будет выглядеть так:

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

Если синтаксический анализ выполняется не так, последний тег в файле XML никогда не будет достигнут. Это может быть проблематично, если вы собираетесь вывести другой файл XML в конце whileцикла.

michaelmeyer
источник
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 ... из 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"
Pila
источник