Bagaimana cara mem-parsing XML dalam Bash?

Jawaban:

153

Ini benar-benar hanya sebuah penjelasan dari jawaban Yuzem , tetapi saya tidak merasa bahwa banyak pengeditan ini harus dilakukan kepada orang lain, dan komentar tidak mengizinkan pemformatan, jadi ...

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

Sebut saja "read_dom" alih-alih "rdom", kosongkan sedikit dan gunakan variabel yang lebih panjang:

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

Oke jadi itu mendefinisikan fungsi yang disebut read_dom. Baris pertama membuat IFS (pemisah bidang input) lokal untuk fungsi ini dan mengubahnya ke>. Itu berarti bahwa ketika Anda membaca data alih-alih secara otomatis dipisah pada ruang, tab atau baris baru itu terbagi pada '>'. Baris berikutnya mengatakan untuk membaca input dari stdin, dan bukannya berhenti di baris baru, berhentilah ketika Anda melihat karakter '<' (the -d untuk flag pembatas). Apa yang dibaca kemudian dibagi menggunakan IFS dan ditugaskan ke variabel ENTITY dan CONTENT. Jadi, ambil yang berikut ini:

<tag>value</tag>

Panggilan pertama untuk read_dommendapatkan string kosong (karena '<' adalah karakter pertama). Itu terpecah oleh IFS menjadi hanya '', karena tidak ada karakter '>'. Baca lalu berikan string kosong ke kedua variabel. Panggilan kedua mendapatkan string 'tag> value'. Itu kemudian dibagi oleh IFS menjadi dua kolom 'tag' dan 'value'. Baca lalu tetapkan variabel seperti: ENTITY=tagdan CONTENT=value. Panggilan ketiga mendapat string '/ tag>'. Itu terpecah oleh IFS menjadi dua bidang '/ tag' dan ''. Baca lalu tetapkan variabel seperti: ENTITY=/tagdan CONTENT=. Panggilan keempat akan mengembalikan status bukan nol karena kami telah mencapai akhir file.

Sekarang loop while-nya membersihkan sedikit agar sesuai dengan yang di atas:

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

Baris pertama hanya mengatakan, "sementara fungsi read_dom mengembalikan status nol, lakukan hal berikut." Baris kedua memeriksa apakah entitas yang baru saja kita lihat adalah "judul". Baris berikutnya menggemakan konten tag. Empat garis keluar. Jika itu bukan entitas judul maka loop berulang di baris keenam. Kami mengarahkan "xhtmlfile.xhtml" ke input standar (untuk read_domfungsi) dan mengarahkan output standar ke "titleOfXHTMLPage.txt" (gema dari sebelumnya dalam loop).

Sekarang diberi yang berikut (mirip dengan apa yang Anda dapatkan dari daftar ember di S3) untuk 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>

dan loop berikut:

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

Anda harus mendapatkan:

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

Jadi jika kita menulis sebuah whileloop seperti Yuzem:

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

Kami akan mendapatkan daftar semua file di keranjang S3.

EDIT Jika karena alasan tertentu local IFS=\>tidak berhasil untuk Anda dan Anda menyetelnya secara global, Anda harus mengatur ulang di akhir fungsi seperti:

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

Jika tidak, setiap baris yang Anda lakukan nanti dalam skrip akan kacau.

EDIT 2 Untuk membagi pasangan nama / nilai atribut Anda dapat menambahkan read_dom()seperti:

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

Kemudian tulis fungsi Anda untuk menguraikan dan mendapatkan data yang Anda inginkan seperti ini:

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
}

Kemudian saat Anda read_dommenelepon parse_dom:

while read_dom; do
    parse_dom
done

Kemudian diberikan contoh markup berikut:

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

Anda harus mendapatkan hasil ini:

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

EDIT 3 pengguna lain mengatakan mereka mengalami masalah dengan itu di FreeBSD dan menyarankan menyimpan status keluar dari membaca dan mengembalikannya di akhir read_dom seperti:

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

Saya tidak melihat alasan mengapa itu tidak berhasil

chad
sumber
2
Jika Anda membuat IFS (pemisah bidang input) global, Anda harus meresetnya kembali ke nilai aslinya di akhir, saya mengedit jawaban untuk memilikinya. Kalau tidak, setiap input lain yang Anda lakukan nanti dalam skrip Anda akan kacau. Saya menduga alasan lokal tidak berfungsi untuk Anda adalah karena Anda menggunakan bash dalam mode kompatibilitas (seperti shbang Anda adalah #! / Bin / sh) atau ini adalah versi kuno dari bash.
chad
30
Hanya karena Anda dapat menulis parser Anda sendiri, bukan berarti Anda harus melakukannya.
Stephen Niedzielski
1
@chad tentu mengatakan sesuatu tentang AWS' alur kerja / implementasi yang saya sedang mencari jawaban untuk 'pesta xml' juga wget isi ember S3!
Alastair
2
@Alastair lihat github.com/chad3814/s3scripts untuk satu set skrip bash yang kita gunakan untuk memanipulasi objek S3
chad
5
Menetapkan IFS dalam variabel lokal rapuh dan tidak perlu. Lakukan IFS=\< read ...saja:, yang hanya akan mengatur IFS untuk panggilan baca. (Perhatikan bahwa saya sama sekali tidak mendukung praktik penggunaan readuntuk mem-parsing xml, dan saya percaya melakukan hal itu penuh dengan bahaya dan harus dihindari.)
William Pursell
64

Anda dapat melakukannya dengan sangat mudah hanya menggunakan bash. Anda hanya perlu menambahkan fungsi ini:

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

Sekarang Anda dapat menggunakan rdom seperti baca tetapi untuk dokumen html. Ketika dipanggil rdom akan menetapkan elemen ke variabel E dan konten ke var C.

Misalnya, untuk melakukan apa yang ingin Anda lakukan:

while rdom; do
    if [[ $E = title ]]; then
        echo $C
        exit
    fi
done < xhtmlfile.xhtml > titleOfXHTMLPage.txt
Yuzem
sumber
Bisakah Anda menjelaskan hal ini? Saya berani bertaruh bahwa itu sangat jelas bagi Anda .. dan ini bisa menjadi jawaban yang bagus - jika saya tahu apa yang Anda lakukan di sana .. dapatkah Anda memecahnya sedikit lagi, mungkin menghasilkan beberapa sampel output?
Alex Grey
1
Dipercayai dengan aslinya - one-liner ini sangat elegan dan menakjubkan.
maverick
1
hack besar, tetapi saya harus menggunakan tanda kutip ganda seperti gema "$ C" untuk mencegah ekspansi shell dan interpretasi yang benar dari garis akhir (tergantung pada pengkondisiannya)
user311174
8
Parsing XML dengan grep dan awk tidak oke . Ini mungkin kompromi yang dapat diterima jika XML cukup sederhana dan Anda tidak punya banyak waktu, tetapi itu tidak bisa disebut solusi yang baik.
peterh
59

Alat baris perintah yang dapat dipanggil dari skrip shell meliputi:

  • 4xpath - pembungkus baris perintah di sekitar paket 4Suite Python
  • XMLStarlet
  • xpath - pembungkus baris perintah di sekitar perpustakaan XPath Perl
  • Xidel - Bekerja dengan URL serta file. Juga bekerja dengan JSON

Saya juga menggunakan xmllint dan xsltproc dengan sedikit skrip transformasi XSL untuk melakukan pemrosesan XML dari baris perintah atau dalam skrip shell.

Nat
sumber
2
Di mana saya dapat mengunduh 'xpath' atau '4xpath' dari?
Opher
3
ya, suara / permintaan kedua - tempat untuk mengunduh alat-alat itu, atau apakah Anda maksudnya harus menulis bungkus secara manual? Saya lebih suka tidak membuang waktu melakukan itu kecuali perlu.
David
4
sudo apt-get install libxml-xpath-perl
Andrew Wagner
22

Anda dapat menggunakan utilitas xpath. Itu diinstal dengan paket Perl XML-XPath.

Pemakaian:

/usr/bin/xpath [filename] query

atau XMLStarlet . Untuk menginstalnya di opensuse gunakan:

sudo zypper install xmlstarlet

atau coba cnf xmldi platform lain.

Grisha
sumber
5
Menggunakan xml starlet jelas merupakan pilihan yang lebih baik daripada menulis serializer sendiri (seperti yang disarankan dalam jawaban lain).
Bruno von Paris
Pada banyak sistem, xpathyang sudah diinstal sebelumnya tidak cocok untuk digunakan sebagai komponen dalam skrip. Lihat misalnya stackoverflow.com/questions/15461737/… untuk penjelasan.
tripleee
2
Di Ubuntu / Debianapt-get install xmlstarlet
rubo77
12

Ini cukup ...

xpath xhtmlfile.xhtml '/html/head/title/text()' > titleOfXHTMLPage.txt
teknopaul
sumber
Terima kasih, cepat dan lakukan pekerjaan untuk saya
Miguel Mota
1
Dalam debian apt-get install libxml-xpath-perl.
tres.14159
bekerja seperti pesona
Alexandru-Mihai Manolescu
5

mulai dari jawaban chad, berikut ini adalah solusi kerja COMPLETE untuk mengurai UML, dengan penanganan komentar yang tepat, dengan hanya 2 fungsi kecil (lebih dari 2 bu Anda dapat mencampur semuanya). Saya tidak mengatakan chad tidak berfungsi sama sekali, tetapi memiliki terlalu banyak masalah dengan file XML yang diformat dengan buruk: Jadi Anda harus sedikit lebih rumit untuk menangani komentar dan spasi yang salah tempat / CR / TAB / dll.

Tujuan dari jawaban ini adalah untuk memberikan fungsi ready-2-use, out of the box untuk siapa saja yang membutuhkan parsing UML tanpa alat kompleks menggunakan perl, python atau apa pun. Bagi saya, saya tidak dapat menginstal cpan, atau modul perl untuk OS produksi lama yang saya kerjakan, dan python tidak tersedia.

Pertama, definisi kata-kata UML yang digunakan dalam posting ini:

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

EDIT: fungsi yang diperbarui, dengan pegangan:

  • Xsp Websphere (atribut xmi dan xmlns)
  • harus memiliki terminal yang kompatibel dengan 256 warna
  • 24 warna abu-abu
  • kompatibilitas ditambahkan untuk IBM AIX bash 3.2.16 (1)

Fungsinya, pertama adalah xml_read_dom yang disebut secara rekursif oleh 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
}

dan yang kedua:

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
}

dan terakhir, fungsi rtrim, trim dan 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; }

Pewarnaan:

oh dan Anda akan memerlukan beberapa variabel dinamis pewarnaan rapi untuk didefinisikan pada awalnya, dan diekspor juga:

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

Cara memuat semua itu:

Entah Anda tahu cara membuat fungsi dan memuatnya melalui FPATH (ksh) atau emulasi FPATH (bash)

Jika tidak, cukup salin / tempel semua yang ada di baris perintah.

Bagaimana cara kerjanya:

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

Dengan mode Debug (-d) komentar dan atribut yang diuraikan dicetak ke stderr

pemulung
sumber
Saya mencoba menggunakan dua fungsi di atas yang menghasilkan yang berikut ./read_xml.sh: line 22: (-1): substring expression < 0:?
khmarbaise
Baris 22:[ "x${ATTRIBUTES:(-1):1}x" == "x?x" ] ...
khmarbaise
maaf khmarbaise, ini adalah fungsi bash shell. Jika Anda ingin mengadaptasi mereka sebagai skrip shell, Anda tentu harus mengharapkan beberapa adaptasi kecil! Juga fungsi yang diperbarui menangani kesalahan Anda;)
pemulung
4

Saya tidak mengetahui adanya alat parsing XML shell murni. Jadi Anda kemungkinan besar akan membutuhkan alat yang ditulis dalam bahasa lain.

Modul XML :: Twig Perl saya dilengkapi dengan alat seperti ini:, di xml_grepmana Anda mungkin akan menulis apa yang Anda inginkan xml_grep -t '/html/head/title' xhtmlfile.xhtml > titleOfXHTMLPage.txt( -topsi memberi Anda hasilnya sebagai teks, bukan xml)

mirod
sumber
4

Alat baris perintah lainnya adalah Xidel baru saya . Ini juga mendukung XPath 2 dan XQuery, bertentangan dengan xpath / xmlstarlet yang telah disebutkan.

Judulnya bisa dibaca seperti:

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

Dan itu juga memiliki fitur keren untuk mengekspor beberapa variabel ke bash. Sebagai contoh

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

set $titleke judul dan $imgcountjumlah gambar dalam file, yang harus sefleksibel parsing langsung di bash.

BeniBela
sumber
Inilah yang saya butuhkan! :)
Thomas Daugaard
2

Nah, Anda bisa menggunakan utilitas xpath. Saya kira perl's XML :: Xpath mengandungnya.

alamar
sumber
2

Setelah beberapa penelitian untuk terjemahan antara Linux dan format Windows jalur file dalam file XML saya menemukan tutorial dan solusi menarik pada:

pengguna485380
sumber
2

Walaupun ada beberapa utilitas konsol siap pakai yang mungkin melakukan apa yang Anda inginkan, mungkin akan memakan waktu lebih sedikit untuk menulis beberapa baris kode dalam bahasa pemrograman tujuan umum seperti Python yang dapat dengan mudah diperluas dan disesuaikan dengan kebutuhanmu.

Berikut ini adalah skrip python yang digunakan lxmluntuk parsing - dibutuhkan nama file atau URL sebagai parameter pertama, ekspresi XPath sebagai parameter kedua, dan mencetak string / node yang cocok dengan ekspresi yang diberikan.

Contoh 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))

lxmldapat diinstal dengan pip install lxml. Di ubuntu Anda bisa menggunakan sudo apt install python-lxml.

Pemakaian

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

lxml juga menerima URL sebagai input:

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

Catatan : Jika XML Anda memiliki namespace default tanpa awalan (mis. xmlns=http://abc...) Maka Anda harus menggunakan pawalan (disediakan oleh 'hack') dalam ekspresi Anda, misalnya //p:moduleuntuk mendapatkan modul dari pom.xmlfile. Jika pawalan sudah dipetakan dalam XML Anda, maka Anda harus memodifikasi skrip untuk menggunakan awalan lain.


Contoh 2

Skrip satu kali yang melayani tujuan sempit mengekstraksi nama modul dari file apache maven. Perhatikan bagaimana nama simpul ( module) diawali dengan namespace default {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
sumber
Ini luar biasa ketika Anda ingin menghindari menginstal paket tambahan atau tidak memiliki akses. Pada mesin build, saya dapat membenarkan pip installover apt-getatau yumpanggilan ekstra . Terima kasih!
E. Moffat
0

Metode Yuzem dapat ditingkatkan dengan membalik urutan <dan >tanda - tanda dalam rdomfungsi dan tugas variabel, sehingga:

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

menjadi:

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

Jika parsing tidak dilakukan seperti ini, tag terakhir dalam file XML tidak pernah tercapai. Ini bisa bermasalah jika Anda berniat untuk menghasilkan file XML lain di akhir whileloop.

michaelmeyer
sumber
0

Ini berfungsi jika Anda menginginkan atribut 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
Steven Penny
sumber
-1

Meskipun sepertinya "tidak pernah menguraikan XML, JSON ... dari bash tanpa alat yang tepat" adalah saran yang bagus, saya tidak setuju. Jika ini adalah pekerjaan sampingan, itu adalah pinggang untuk mencari alat yang tepat, kemudian mempelajarinya ... Awk dapat melakukannya dalam hitungan menit. Program saya harus mengerjakan semua data yang disebutkan di atas dan lebih banyak lagi. Sial, saya tidak ingin menguji 30 alat untuk mem-parsing 5-7-10 format yang berbeda yang saya butuhkan jika saya dapat mengatasi masalah dalam hitungan menit. Saya tidak peduli dengan XML, JSON atau apa pun! Saya butuh solusi tunggal untuk semuanya.

Sebagai contoh: Program SmartHome saya mengelola rumah kami. Saat melakukannya, ia membaca sejumlah besar data dalam berbagai format berbeda yang tidak dapat saya kendalikan. Saya tidak pernah menggunakan alat khusus yang berdedikasi karena saya tidak ingin menghabiskan lebih dari beberapa menit untuk membaca data yang saya butuhkan. Dengan penyesuaian FS dan RS, solusi awk ini berfungsi sempurna untuk semua format teks. Tapi, itu mungkin bukan jawaban yang tepat ketika tugas utama Anda adalah bekerja terutama dengan banyak data dalam format itu!

Masalah parsing XML dari bash yang saya hadapi kemarin. Inilah cara saya melakukannya untuk format data hierarkis apa pun. Sebagai bonus - saya menetapkan data langsung ke variabel dalam skrip bash.

Agar lebih mudah dibaca, saya akan menyajikan solusi secara bertahap. Dari data tes OP, saya membuat file: test.xml

Parsing mengatakan XML dalam bash dan mengekstraksi data dalam 90 karakter:

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

Saya biasanya menggunakan versi yang lebih mudah dibaca karena lebih mudah untuk dimodifikasi dalam kehidupan nyata karena saya sering perlu menguji secara berbeda:

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

Saya tidak peduli bagaimana formatnya. Saya hanya mencari solusi paling sederhana. Dalam kasus khusus ini, saya dapat melihat dari data bahwa baris baru adalah pemisah rekaman (RS) dan bidang pembatas <> (FS). Dalam kasus asli saya, saya memiliki pengindeksan rumit dari 6 nilai dalam dua catatan, yang berkaitan dengan mereka, temukan ketika data ada ditambah bidang (catatan) mungkin atau mungkin tidak ada. Butuh 4 baris awk untuk menyelesaikan masalah dengan sempurna. Jadi, sesuaikan ide dengan setiap kebutuhan sebelum menggunakannya!

Bagian kedua hanya terlihat ada string yang diinginkan dalam garis (RS) dan jika demikian, mencetak bidang yang diperlukan (FS). Di atas butuh waktu sekitar 30 detik untuk menyalin dan beradaptasi dari perintah terakhir yang saya gunakan dengan cara ini (4 kali lebih lama). Dan itu dia! Dilakukan dalam 90 karakter.

Tapi, saya selalu perlu memasukkan data ke dalam variabel dalam skrip saya. Saya pertama kali menguji konstruksi seperti:

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

Dalam beberapa kasus saya menggunakan printf daripada print. Ketika saya melihat semuanya terlihat baik, saya hanya selesai menetapkan nilai ke variabel. Saya tahu banyak yang berpikir "eval" adalah "jahat", tidak perlu berkomentar :) Trik bekerja dengan baik pada keempat jaringan saya selama bertahun-tahun. Tetapi teruslah belajar jika Anda tidak mengerti mengapa ini bisa menjadi praktik yang buruk! Termasuk tugas variabel bash dan spasi yang cukup, solusi saya perlu 120 karakter untuk melakukan semuanya.

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
sumber