Как разбирать 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)


На самом деле это просто объяснение ответа Юзема, но я не чувствовал, что такое большое редактирование должно быть сделано для кого-то еще, а комментарии не допускают форматирования, поэтому ...

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 разделяет это на просто "", поскольку нет символа ">". Затем чтение присваивает обеим переменным пустую строку. Второй вызов получает строку 'tag> value'. 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 возвращает нулевой статус, сделайте следующее». Вторая строка проверяет, является ли сущность, которую мы только что видели, "title". Следующая строка повторяет содержимое тега. Четыре линии выходов. Если это не объект заголовка, цикл повторяется на шестой строке. Мы перенаправляем «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, как у Юзема:

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_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 У меня есть целый набор сценариев bash для манипуляций с S3, я спрошу своего менеджера, могу ли я их выпустить. - person chad; 10.10.2013
comment
@Alastair см. github.com/chad3814/s3scripts для набора сценариев bash, которые мы используем для управления S3. объекты - person chad; 11.10.2013
comment
Вклад Гроккина, @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, а содержимое - переменной 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 - оболочка командной строки для библиотеки Perl XPath

    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 install 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

начиная с ответа Чада, вот ПОЛНОЕ рабочее решение для синтаксического анализа UML с правильной обработкой комментариев, всего с двумя небольшими функциями (более 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. Если вы хотите адаптировать их как сценарии оболочки, вам, безусловно, следует ожидать некоторых незначительных изменений! Также обновленные функции обрабатывают ваши ошибки;) - 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. Я предполагаю, что Perl XML :: Xpath содержит его.

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 (предоставленный '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)
person ccpizza    schedule 25.10.2017
comment
Это замечательно, когда вы либо хотите избежать установки дополнительных пакетов, либо не имеете доступа к ним. На сборочной машине я могу оправдать дополнительный pip install вызов по apt-get или yum. Спасибо! - person E. Moffat; 31.10.2018

Метод Юзема можно улучшить, изменив порядок знаков < и > в функции 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 обслуживает наши дома. При этом он читает множество данных в слишком большом количестве различных форматов, которые я не могу контролировать. Я никогда не использую специальные, подходящие инструменты, так как не хочу тратить больше минут на чтение необходимых мне данных. Это awk-решение с настройками FS и RS отлично работает с любым текстовым форматом. Но это может быть неправильный ответ, когда ваша основная задача - работать в основном с большим количеством данных в этом формате!

С проблемой синтаксического анализа 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
... так что, если вы хотите сделать это безопасным, вам нужно both (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