Jak analizować XML w Bash?


Odpowiedzi:


153

To naprawdę tylko wyjaśnienie odpowiedzi Yuzem , ale nie sądziłem, że tak wiele edycji powinno się robić komuś innemu, a komentarze nie pozwalają na formatowanie, więc ...

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

Nazwijmy to „read_dom” zamiast „rdom”, oddzielmy to trochę i używaj dłuższych zmiennych:

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

OK, więc definiuje funkcję o nazwie read_dom. Pierwsza linia ustawia IFS (separator pól wejściowych) jako lokalny dla tej funkcji i zmienia go na>. Oznacza to, że kiedy czytasz dane, zamiast automatycznie dzielić je na spację, tabulację lub znaki nowej linii, są one dzielone na „>”. Następna linia mówi, że należy czytać dane wejściowe ze standardowego wejścia i zamiast zatrzymywać się na nowej linii, zatrzymaj się, gdy zobaczysz znak „<” (-d dla flagi separatora). To, co jest odczytywane, jest następnie dzielone za pomocą IFS i przypisywane do zmiennej ENTITY i CONTENT. Więc weź następujące:

<tag>value</tag>

Pierwsze wywołanie w celu read_domuzyskania pustego ciągu (ponieważ „<” jest pierwszym znakiem). To zostaje podzielone przez IFS na po prostu „”, ponieważ nie ma znaku „>”. Read następnie przypisuje pusty ciąg do obu zmiennych. Drugie wywołanie pobiera ciąg „tag> wartość”. To zostaje następnie podzielone przez IFS na dwa pola „tag” i „wartość”. Read następnie przypisuje zmienne, takie jak: ENTITY=tagi CONTENT=value. Trzecie wywołanie otrzymuje ciąg „/ tag>”. To zostaje podzielone przez IFS na dwa pola „/ tag” i „”. Read następnie przypisuje zmienne, takie jak: ENTITY=/tagi CONTENT=. Czwarte wywołanie zwróci stan niezerowy, ponieważ dotarliśmy do końca pliku.

Teraz jego pętla while została nieco wyczyszczona, aby pasowała do powyższego:

while read_dom; do
    if [[ $ENTITY = "title" ]]; then
        echo $CONTENT
        exit
    fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt

Pierwsza linia mówi po prostu, „podczas gdy funkcja read_dom zwraca stan zerowy, wykonaj następujące czynności”. Druga linia sprawdza, czy jednostka, którą właśnie widzieliśmy, to „tytuł”. W następnym wierszu zostanie wyświetlona treść tagu. Cztery linie wychodzą. Jeśli nie był to tytuł, pętla powtarza się w szóstym wierszu. Przekierowujemy „xhtmlfile.xhtml” na standardowe wejście (dla read_domfunkcji) i przekierowujemy standardowe wyjście do „titleOfXHTMLPage.txt” (echo z wcześniejszej części pętli).

Teraz biorąc pod uwagę następujące informacje (podobne do tego, co otrzymujesz z umieszczenia wiadra na S3) dla 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>

i następującą pętlę:

while read_dom; do
    echo "$ENTITY => $CONTENT"
done < input.xml

Powinieneś wziąć:

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

Jeśli więc napisaliśmy whilepętlę taką jak Yuzem:

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

Otrzymalibyśmy listę wszystkich plików w zasobniku S3.

EDYCJA Jeśli z jakiegoś powodu local IFS=\>nie działa u Ciebie i ustawiasz ją globalnie, powinieneś zresetować ją na końcu funkcji, np .:

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

W przeciwnym razie każdy podział linii, który wykonasz później w skrypcie, będzie pomieszany.

EDYCJA 2 Aby rozdzielić pary nazwa / wartość atrybutu, możesz rozszerzyć w ten read_dom()sposób:

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

Następnie napisz swoją funkcję, aby przeanalizować i uzyskać dane, które chcesz w ten sposób:

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
}

Następnie podczas read_domrozmowy parse_dom:

while read_dom; do
    parse_dom
done

Następnie, biorąc pod uwagę następujący przykładowy znacznik:

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

Powinieneś otrzymać to wyjście:

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

EDIT 3 inny użytkownik powiedział, że ma z tym problemy we FreeBSD i zasugerował zapisanie statusu wyjścia z odczytu i zwrócenie go na końcu read_dom, na przykład:

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

Nie widzę powodu, dla którego to nie powinno działać


2
Jeśli ustawisz IFS (separator pól wejściowych) jako globalny, powinieneś zresetować go z powrotem do jego pierwotnej wartości na końcu, zredagowałem odpowiedź, aby to mieć. W przeciwnym razie jakikolwiek inny podział danych wejściowych, który wykonasz później w skrypcie, będzie pomieszany. Podejrzewam, że powodem, dla którego lokalny nie działa, jest to, że albo używasz basha w trybie zgodności (jak twój shbang to #! / Bin / sh), albo jest to starożytna wersja bash.
czad

30
To, że możesz napisać własny parser, nie oznacza, że ​​powinieneś.
Stephen Niedzielski

1
@chad to z pewnością mówi coś o przepływie pracy / implementacji AWS, że szukałem odpowiedzi na "bash xml", aby również pobrać zawartość wiadra S3!
Alastair

2
@Alastair zobacz github.com/chad3814/s3scripts, aby zapoznać się z zestawem skryptów bash, których używamy do manipulowania obiektami S3
czad

5
Przypisywanie IFS do zmiennej lokalnej jest kruche i niepotrzebne. Po prostu zrób:, IFS=\< read ...co ustawi IFS tylko dla wywołania read. (Zauważ, że w żaden sposób nie popieram praktyki używania readdo parsowania xml i uważam, że jest to najeżone niebezpieczeństwem i powinno się go unikać.)
William Pursell

64

Możesz to zrobić bardzo łatwo używając samego basha. Musisz tylko dodać tę funkcję:

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

Teraz możesz używać rdom jak read, ale dla dokumentów html. Po wywołaniu rdom przypisze element do zmiennej E, a zawartość do zmiennej C.

Na przykład, aby zrobić to, co chciałeś:

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

czy mógłbyś to rozwinąć? Założę się, że jest to dla ciebie zupełnie jasne ... i to może być świetna odpowiedź - gdybym mógł powiedzieć, co tam robisz ... czy możesz to trochę bardziej rozłożyć, być może generując jakieś próbki wyjściowe?
Alex Grey

1
Podziękowania dla oryginału - ten jednoliniowy jest tak cholernie elegancki i niesamowity.
Maverick

1
świetny hack, ale musiałem użyć podwójnych cudzysłowów, takich jak echo "$ C", aby zapobiec rozwijaniu powłoki i poprawnej interpretacji linii końcowych (zależy od kodowania)
user311174

8
Parsowanie XML za pomocą grep i awk nie jest w porządku . Może to być akceptowalny kompromis, jeśli XML jest wystarczająco prosty i nie masz za dużo czasu, ale nigdy nie można go nazwać dobrym rozwiązaniem.
peterh - Przywróć Monikę

59

Narzędzia wiersza polecenia, które można wywołać ze skryptów powłoki, obejmują:

  • 4xpath - opakowanie wiersza poleceń wokół pakietu 4Suite Pythona
  • XMLStarlet
  • xpath - opakowanie wiersza poleceń wokół biblioteki XPath Perla
  • Xidel - działa z adresami URL oraz plikami. Działa również z JSON

Używam również xmllint i xsltproc z małymi skryptami transformacji XSL do przetwarzania XML z wiersza poleceń lub w skryptach powłoki.


2
Skąd mogę pobrać „xpath” lub „4xpath”?
Opher

3
tak, drugie głosowanie / prośba - skąd pobrać te narzędzia, czy to znaczy, że trzeba ręcznie napisać opakowanie? Wolałbym nie tracić czasu na robienie tego, chyba że jest to konieczne.
David

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

22

Możesz użyć narzędzia xpath. Jest instalowany z pakietem Perl XML-XPath.

Stosowanie:

/usr/bin/xpath [filename] query

lub XMLStarlet . Aby zainstalować go na opensuse użyj:

sudo zypper install xmlstarlet

lub wypróbuj cnf xmlna innych platformach.


5
Korzystanie z xml starlet jest zdecydowanie lepszą opcją niż pisanie własnego serializatora (jak sugerowano w innych odpowiedziach).
Bruno von Paris,

W wielu systemach xpathpreinstalowany element nie nadaje się do użycia jako składnik skryptów. Zobacz np. Stackoverflow.com/questions/15461737/… w celu uzyskania dalszych informacji.
tripleee

2
Na Ubuntu / Debianapt-get install xmlstarlet
rubo77,



5

zaczynając od odpowiedzi czada, oto KOMPLETNIE działające rozwiązanie do parsowania UML, z właściwą obsługą komentarzy, z zaledwie 2 małymi funkcjami (więcej niż 2, ale możesz je wszystkie mieszać). Nie mówię, że jeden z Chada w ogóle nie działał, ale miał zbyt wiele problemów ze źle sformatowanymi plikami XML: Więc musisz być nieco trudniejszy, aby radzić sobie z komentarzami i niewłaściwymi spacjami / CR / TAB / itp.

Celem tej odpowiedzi jest udostępnienie gotowych do użycia, gotowych do użycia funkcji bash każdemu, kto potrzebuje analizować UML bez skomplikowanych narzędzi przy użyciu perla, pythona lub czegokolwiek innego. Jeśli chodzi o mnie, nie mogę zainstalować modułów cpan ani perl dla starego produkcyjnego systemu operacyjnego, na którym pracuję, a Python nie jest dostępny.

Najpierw definicja słów UML użytych w tym poście:

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

EDYCJA: zaktualizowane funkcje, z uchwytem:

  • Websphere xml (atrybuty xmi i xmlns)
  • musi mieć kompatybilny terminal z 256 kolorami
  • 24 odcienie szarości
  • kompatybilność dodana dla IBM AIX bash 3.2.16 (1)

Funkcje, pierwsza to xml_read_dom, który jest wywoływany rekurencyjnie przez 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
}

a drugi:

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
}

i na koniec funkcje rtrim, trim i 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; }

Koloryzacja:

Och, a będziesz potrzebować kilku zgrabnych, kolorujących zmiennych dynamicznych, które zostaną najpierw zdefiniowane i wyeksportowane:

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"'

Jak załadować to wszystko:

Albo wiesz, jak tworzyć funkcje i ładować je przez FPATH (ksh) lub emulację FPATH (bash)

Jeśli nie, po prostu skopiuj / wklej wszystko w wierszu poleceń.

Jak to działa:

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

W trybie debugowania (-d) komentarze i przeanalizowane atrybuty są wypisywane na stderr


Próbuję użyć dwóch powyższych funkcji, które dają następujące wyniki ./read_xml.sh: line 22: (-1): substring expression < 0:?
khmarbaise

Linia 22:[ "x${ATTRIBUTES:(-1):1}x" == "x?x" ] ...
khmarbaise

przepraszam khmarbaise, to są funkcje powłoki bash. Jeśli chcesz zaadaptować je jako skrypty powłoki, z pewnością musisz spodziewać się drobnych dostosowań! Również zaktualizowane funkcje obsługują twoje błędy;)
scavenger

4

Nie znam żadnego narzędzia do analizowania czystego XML-a. Najprawdopodobniej będziesz potrzebować narzędzia napisanego w innym języku.

Mój moduł XML :: Twig Perl zawiera takie narzędzie:, xml_grepgdzie prawdopodobnie napiszesz to, co chcesz xml_grep -t '/html/head/title' xhtmlfile.xhtml > titleOfXHTMLPage.txt( -topcja daje wynik jako tekst zamiast xml)


4

Kolejnym narzędziem wiersza poleceń jest mój nowy Xidel . Obsługuje również XPath 2 i XQuery, w przeciwieństwie do wspomnianego już xpath / xmlstarlet.

Tytuł można czytać tak:

xidel xhtmlfile.xhtml -e /html/head/title > titleOfXHTMLPage.txt

Ma też fajną funkcję eksportowania wielu zmiennych do basha. Na przykład

eval $(xidel xhtmlfile.xhtml -e 'title := //title, imgcount := count(//img)' --output-format bash )

ustawia $titletytuł i $imgcountliczbę obrazów w pliku, co powinno być tak elastyczne, jak przetwarzanie go bezpośrednio w bashu.


Właśnie tego potrzebowałem! :)
Thomas Daugaard

2

Cóż, możesz użyć narzędzia xpath. Wydaje mi się, że zawiera go XML :: Xpath Perla.


2

Po poszukiwaniach tłumaczenia między formatami Linux i Windows ścieżek plików w plikach XML znalazłem ciekawe samouczki i rozwiązania dotyczące:


2

Chociaż istnieje sporo gotowych narzędzi konsolowych, które mogą robić to, co chcesz, prawdopodobnie napisanie kilku wierszy kodu w języku programowania ogólnego przeznaczenia, takim jak Python, zajmie prawdopodobnie mniej czasu, który można łatwo rozszerzyć i dostosować do Twoje potrzeby.

Oto skrypt w Pythonie, który używa lxmldo analizy - przyjmuje nazwę pliku lub adres URL jako pierwszy parametr, wyrażenie XPath jako drugi parametr i drukuje ciągi / węzły pasujące do danego wyrażenia.

Przykład 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))

lxmlmożna zainstalować z pip install lxml. Na Ubuntu możesz użyć sudo apt install python-lxml.

Stosowanie

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

lxml akceptuje również adres URL jako dane wejściowe:

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

Uwaga : Jeśli twój XML ma domyślną przestrzeń nazw bez prefiksu (np. xmlns=http://abc...), Musisz użyć pprzedrostka (dostarczonego przez 'hack') w swoich wyrażeniach, np. //p:moduleAby pobrać moduły z pom.xmlpliku. W przypadku, gdy pprefiks jest już odwzorowany w Twoim XML, musisz zmodyfikować skrypt, aby używał innego prefiksu.


Przykład 2

Jednorazowy skrypt, który służy wąskim celom wyodrębniania nazw modułów z pliku maven Apache. Zwróć uwagę, jak nazwa węzła ( module) jest poprzedzona domyślną przestrzenią nazw {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)

Jest to niesamowite, gdy chcesz uniknąć instalowania dodatkowych pakietów lub nie masz do nich dostępu. Na maszynie budowlanej, mogę usprawiedliwić dodatkowy pip installover apt-getlub yumpołączenia. Dzięki!
E. Moffat

0

Metodę Yuzema można ulepszyć odwracając kolejność znaków <i >w rdomfunkcji oraz przypisania zmiennych, tak aby:

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

staje się:

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

Jeśli parsowanie nie zostanie wykonane w ten sposób, ostatni znacznik w pliku XML nigdy nie zostanie osiągnięty. Może to być problematyczne, jeśli zamierzasz wyprowadzić inny plik XML na końcu whilepętli.


0

Działa to, jeśli potrzebujesz atrybutów 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

Chociaż wydaje się, że „nigdy nie parsuj XML, JSON… z basha bez odpowiedniego narzędzia” to rozsądna rada, nie zgadzam się. Jeśli to praca poboczna, warto poszukać odpowiedniego narzędzia, a potem się tego nauczyć ... Awk może to zrobić w kilka minut. Moje programy muszą pracować na wszystkich wyżej wymienionych i na wielu innych rodzajach danych. Do diabła, nie chcę testować 30 narzędzi do analizowania 5-7-10 różnych formatów, których potrzebuję, jeśli mogę rozwiązać problem w ciągu kilku minut. Nie obchodzi mnie XML, JSON czy cokolwiek! Potrzebuję jednego rozwiązania dla wszystkich.

Na przykład: mój program SmartHome obsługuje nasze domy. Robiąc to, czyta mnóstwo danych w zbyt wielu różnych formatach, których nie mogę kontrolować. Nigdy nie używam dedykowanych, odpowiednich narzędzi, ponieważ nie chcę spędzać więcej niż minuty na odczytywaniu potrzebnych mi danych. Dzięki dostosowaniom FS i RS to rozwiązanie awk działa doskonale dla każdego formatu tekstowego. Jednak może to nie być właściwa odpowiedź, gdy Twoim głównym zadaniem jest praca głównie z dużą ilością danych w tym formacie!

Problem z parsowaniem XML z bash napotkałem wczoraj. Oto jak to robię dla dowolnego hierarchicznego formatu danych. Jako bonus - przypisuję dane bezpośrednio do zmiennych w skrypcie basha.

Dla ułatwienia czytania przedstawię rozwiązanie etapami. Z danych testowych OP utworzyłem plik: test.xml

Przetwarzanie wspomnianego XML w bashu i wyodrębnianie danych w 90 znakach:

awk 'BEGIN { FS="<|>"; RS="\n" }; /host|username|password|dbname/ { print $2, $4 }' test.xml

Zwykle używam bardziej czytelnej wersji, ponieważ łatwiej jest ją modyfikować w prawdziwym życiu, ponieważ często muszę testować inaczej:

awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2,$4}' test.xml

Nie obchodzi mnie, jak nazywa się ten format. Szukam tylko najprostszego rozwiązania. W tym konkretnym przypadku widzę z danych, że znak nowej linii jest separatorem rekordów (RS) i <> ogranicznikami pól (FS). W moim pierwotnym przypadku miałem skomplikowane indeksowanie 6 wartości w dwóch rekordach, odnosząc je, znajdując, kiedy dane istnieją oraz pola (rekordy) mogą istnieć lub nie. Idealne rozwiązanie problemu zajęło 4 linijki awk. Dlatego dostosuj pomysł do każdej potrzeby, zanim go użyjesz!

Druga część po prostu wygląda na to, że w linii (RS) jest poszukiwany łańcuch i jeśli tak, wypisuje potrzebne pola (FS). Powyższe zajęło mi około 30 sekund, aby skopiować i dostosować od ostatniego polecenia, którego użyłem w ten sposób (4 razy dłużej). I to jest to! Sporządzono w 90 znakach.

Ale zawsze muszę starannie umieścić dane w zmiennych w moim skrypcie. Najpierw testuję konstrukcje w następujący sposób:

awk 'BEGIN { FS="<|>"; RS="\n" }; { if ($0 ~ /host|username|password|dbname/) print $2"=\""$4"\"" }' test.xml

W niektórych przypadkach używam printf zamiast print. Kiedy widzę, że wszystko wygląda dobrze, po prostu kończę przypisywanie wartości do zmiennych. Wiem, że wielu uważa, że ​​„eval” jest „zły”, nie ma potrzeby komentowania :) Trick działa doskonale we wszystkich czterech moich sieciach od lat. Ale ucz się dalej, jeśli nie rozumiesz, dlaczego może to być zła praktyka! Uwzględniając przypisania zmiennych bash i duże odstępy, moje rozwiązanie potrzebuje 120 znaków, aby zrobić wszystko.

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"
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.