Bash'de XML nasıl ayrıştırılır?


Yanıtlar:


153

Bu gerçekten sadece Yuzem'in cevabının bir açıklaması , ama bu kadar düzenlemenin başka birine yapılması gerektiğini hissetmedim ve yorumlar biçimlendirmeye izin vermiyor, bu yüzden ...

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

"Rdom" yerine "read_dom" diyelim, biraz boşluk bırakalım ve daha uzun değişkenler kullanalım:

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

Tamam, read_dom adlı bir işlevi tanımlar. İlk satır IFS'yi (giriş alanı ayırıcısı) bu işleve yerel yapar ve> olarak değiştirir. Bu, boşlukta, sekmede veya yeni satırlarda otomatik olarak bölünmek yerine verileri okuduğunuzda '>' üzerinde bölündüğü anlamına gelir. Bir sonraki satır, stdin'den girdi okuduğunu ve yeni bir satırda durmak yerine, bir '<' karakteri (ayırıcı bayrağı için -d) gördüğünüzde durduğunu söylüyor. Okunan daha sonra IFS kullanılarak bölünür ve ENTITY ve CONTENT değişkenine atanır. Bu yüzden aşağıdakileri yapın:

<tag>value</tag>

read_domBoş bir dize almak için ilk çağrı ('<' ilk karakter olduğundan). '>' Karakteri olmadığı için bu, IFS tarafından sadece '' olarak bölünür. Okuma, ardından her iki değişkene de boş bir dize atar. İkinci çağrı 'tag> value' dizesini alır. Bu daha sonra IFS tarafından 'tag' ve 'value' iki alanına bölünür. Okuyun ve sonra: ENTITY=tagve gibi değişkenleri atar CONTENT=value. Üçüncü çağrı '/ tag>' dizesini alır. Bu, IFS tarafından '/ tag' ve '' iki alanına bölünür. Okuyun ve sonra: ENTITY=/tagve gibi değişkenleri atar CONTENT=. Dosya sonuna ulaştığımız için dördüncü çağrı sıfırdan farklı bir durum döndürecektir.

Şimdi while döngüsü yukarıdakilere uyacak şekilde biraz temizlendi:

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

İlk satırda "read_dom işlevi sıfır durumuna dönerken aşağıdakileri yapın" yazıyor. İkinci satır, az önce gördüğümüz varlığın "başlık" olup olmadığını kontrol eder. Sonraki satır, etiketin içeriğini yansıtır. Dört çizgi çıkar. Başlık varlığı değilse, döngü altıncı satırda tekrarlanır. "Xhtmlfile.xhtml" dosyasını standart girdiye ( read_domişlev için) ve standart çıktıyı "titleOfXHTMLPage.txt" 'ye (döngüden önceki yankı) yönlendiririz.

Şimdi aşağıdakiler verilmiştir (S3'te bir kova listelemekten elde ettiğinize benzer) 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>

ve aşağıdaki döngü:

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

Şunları elde etmelisiniz:

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

whileYuzem gibi bir döngü yazarsak:

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

S3 grubundaki tüm dosyaların bir listesini alırız.

DÜZENLE Herhangi bir nedenden dolayı local IFS=\>sizin için işe yaramazsa ve global olarak ayarlarsanız, aşağıdaki gibi işlevin sonunda sıfırlamanız gerekir:

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

Aksi takdirde, komut dosyasında daha sonra yaptığınız herhangi bir satır bölme işlemi bozulacaktır.

DÜZENLEME 2 Özellik adı / değer çiftlerini ayırmak için aşağıdakileri artırabilirsiniz read_dom():

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

Ardından ayrıştırmak ve istediğiniz verileri almak için işlevinizi yazın:

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
}

Sonra read_domaradığınızda parse_dom:

while read_dom; do
    parse_dom
done

Daha sonra aşağıdaki örnek işaretleme verildi:

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

Bu çıktıyı almalısınız:

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

EDIT 3 başka bir kullanıcı , FreeBSD'de bununla ilgili sorun yaşadıklarını söyledi ve çıkış durumunun okunmadan kaydedilmesini ve read_dom'un sonunda geri döndürülmesini önerdi:

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

Bunun çalışmaması için hiçbir neden göremiyorum


2
IFS'yi (giriş alanı ayırıcısı) global yaparsanız, sonunda orijinal değerine geri döndürmelisiniz, cevabı düzenledim. Aksi takdirde, betiğinizde daha sonra yapacağınız diğer girdi bölmeleri berbat olur. Yerelin sizin için işe yaramadığından şüpheleniyorum ya ya bir uyumluluk modunda bash kullanıyorsunuz (şakanız #! / Bin / sh gibi) ya da bash'ın eski bir versiyonudur.
Çad

30
Kendi ayrıştırıcınızı yazabileceğiniz için yapmanız gerektiği anlamına gelmez.
Stephen Niedzielski

1
@ chad kesinlikle bir S3 kova içeriğini wget için "bash xml" yanıt aradığını AWS 'iş akışı / uygulama hakkında bir şey söylüyor !
Alastair

2
@Alastair bkz github.com/chad3814/s3scripts biz S3 nesneleri işlemek için kullandığı bash komut kümesi için
Chad

5
Yerel bir değişkende IFS atamak kırılgandır ve gerekli değildir. Sadece şunu yapın: IFS=\< read ...ki bu sadece okuma çağrısı için IFS'yi ayarlar. (Hiçbir şekilde readxml ayrıştırmak için uygulama uygulamasını onaylamadığımı ve bunun tehlikeyle dolu olduğuna ve kaçınılması gerektiğine inanıyorum.)
William Pursell

64

Bunu sadece bash kullanarak kolayca yapabilirsiniz. Yalnızca bu işlevi eklemeniz gerekir:

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

Artık html'i read gibi ama html belgeleri için kullanabilirsiniz. Rdom çağrıldığında elemanı E değişkenine ve içeriği C değişkenine atar.

Örneğin, yapmak istediğiniz şeyi yapmak için:

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

Bu konuda ayrıntılı misiniz? Bahse girerim size çok açık .. ve bu harika bir cevap olabilir - orada ne yaptığını söyleyebilirdim .. biraz daha bozabilir, muhtemelen bazı örnek çıktı üretebilir?
Alex Gray

1
Orijinaline bakıldığında - bu tek astar çok garip 'zarif ve şaşırtıcı.
maverick

1
büyük kesmek, ama ben kabuk genişlemesi ve bitiş hatlarının doğru yorumlanması önlemek için yankı "$ C" gibi çift tırnak kullanmak zorunda (kaplama bağlıdır)
user311174

8
XML'yi grep ve awk ile ayrıştırmak uygun değildir . XML'ler yeterince basitse ve çok fazla zamanınız yoksa kabul edilebilir bir uzlaşma olabilir, ancak bu asla iyi bir çözüm olarak adlandırılamaz.
peterh - Monica'yı yeniden

59

Kabuk komut dosyalarından çağrılabilen komut satırı araçları şunları içerir:

  • 4xpath - Python'un 4Suite paketinin etrafındaki komut satırı sarmalayıcısı
  • XMLStarlet
  • xpath - Perl'in XPath kütüphanesi etrafındaki komut satırı sarmalayıcısı
  • Xidel - URL'lerin yanı sıra dosyalarla da çalışır. JSON ile de çalışır

Ben de xmllint ve xsltproc komut satırından veya kabuk komut dosyalarında XML işleme yapmak için küçük XSL dönüşüm komut dosyaları ile kullanın.


2
'Xpath' veya '4xpath'i nereden indirebilirim?
Opher

3
evet, ikinci bir oy / istek - bu araçları nereden indirebilirim, ya da elle bir sarıcı yazmak zorunda mısınız? Gerekmedikçe bunu yapmak için zaman harcamak istemem.
David

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

22

Xpath yardımcı programını kullanabilirsiniz. Perl XML-XPath paketiyle birlikte yüklenir.

Kullanımı:

/usr/bin/xpath [filename] query

veya XMLStarlet . Opensuse yüklemek için kullanın:

sudo zypper install xmlstarlet

veya cnf xmldiğer platformlarda deneyin .


5
Xml starlet kullanmak kesinlikle kendi serileştiricisini yazmaktan daha iyi bir seçenektir (diğer cevaplarda önerildiği gibi).
Bruno von Paris

xpathÖnceden yüklenmiş olarak gelen birçok sistemde, komut dosyalarında bileşen olarak kullanım için uygun değildir. Ayrıntılı bilgi için bkz . Stackoverflow.com/questions/15461737/… .
tripleee

2
Ubuntu / Debian hakkındaapt-get install xmlstarlet
rubo77



5

Çad'ın cevabından başlayarak, yorumların propper işlenmesi ile, sadece 2 küçük fonksiyonla (2'den fazla bu hepsini karıştırabilirsiniz) UML'yi ayrıştırmak için TAM çalışma çözümü. Çad'ın hiç işe yaramadığını söylemiyorum, ancak kötü biçimlendirilmiş XML dosyalarıyla çok fazla sorunu vardı: Bu yüzden yorumları ve yanlış yerleştirilmiş alanları / CR / TAB / etc'yi işlemek için biraz daha zor olmalısınız.

Bu cevabın amacı, perl, python veya başka bir şey kullanan karmaşık araçlar olmadan UML'yi ayrıştırma ihtiyacı duyan herkese, kullanıma hazır bash işlevlerini vermektir. Bana gelince, üzerinde çalıştığım eski üretim işletim sistemi için cpan veya perl modülleri yükleyemiyorum ve python mevcut değil.

İlk olarak, bu yayında kullanılan UML kelimelerinin tanımı:

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

EDIT: güncellenmiş işlevler, tutamağı ile:

  • Websphere xml (xmi ve xmlns özellikleri)
  • 256 renkli uyumlu bir terminale sahip olmalıdır
  • 24 gri tonu
  • IBM AIX bash 3.2.16 (1) için uyumluluk eklendi

İşlevler, ilk olarak xml_read tarafından özyinelemeli olarak adlandırılan xml_read_dom'dur:

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
}

ve ikincisi:

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
}

ve son olarak, rtrim, trim ve echo2 (stderr'a) işlev görür:

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; }

renklendirme:

oh ve ilk başta tanımlanacak ve dışa aktarılacak bazı düzgün renklendirme dinamik değişkenlerine ihtiyacınız olacak:

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

Tüm bu şeyler nasıl yüklenir:

Ya fonksiyonlar oluşturmayı ve bunları FPATH (ksh) veya FPATH (bash) öykünmesi yoluyla nasıl yükleyeceğinizi biliyorsunuz

Değilse, komut satırındaki her şeyi kopyalayın / yapıştırın.

O nasıl çalışır:

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

Hata Ayıklama modu (-d) ile yorumlar ve ayrıştırılmış öznitelikler stderr


Ben aşağıdaki üreten yukarıdaki iki işlevi kullanmaya çalışıyorum: ./read_xml.sh: line 22: (-1): substring expression < 0?
khmarbaise

Satır 22:[ "x${ATTRIBUTES:(-1):1}x" == "x?x" ] ...
Khmarbaise

üzgünüm khmarbaise, bunlar bash kabuk fonksiyonları. Bunları kabuk komut dosyaları olarak uyarlamak istiyorsanız, kesinlikle bazı küçük uyarlamalar beklemeniz gerekir! Ayrıca güncellenen fonksiyonlar hatalarınızı idare eder;)
çöpçü

4

Hiçbir saf kabuk XML ayrıştırma aracı farkında değilim. Yani büyük olasılıkla başka bir dilde yazılmış bir araca ihtiyacınız olacak.

XML :: Twig Perl modülü böyle bir araçla birlikte gelir: xml_grepmuhtemelen istediğiniz şeyi yazabilirsiniz xml_grep -t '/html/head/title' xhtmlfile.xhtml > titleOfXHTMLPage.txt( -tseçenek size sonucu xml yerine metin olarak verir)


4

Başka bir komut satırı aracı yeni Xidel'im . Ayrıca, daha önce bahsedilen xpath / xmlstarlet'in aksine XPath 2 ve XQuery'yi de destekler.

Başlık şu şekilde okunabilir:

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

Ayrıca, birden çok değişkeni bash'a aktarmak için harika bir özelliği vardır. Örneğin

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

$titlebaşlığa ve $imgcountdoğrudan bash içinde ayrıştırmak kadar esnek olması gereken dosyadaki görüntü sayısına ayarlanır .


Tam da ihtiyacım olan şey bu! :)
Thomas Daugaard

2

Peki, xpath yardımcı programını kullanabilirsiniz. Perl XML XML Xpath içeriyor sanırım.


2

XML dosyalarındaki dosya yollarının Linux ve Windows formatları arasındaki çeviri araştırmalarından sonra ilginç öğreticiler ve çözümler buldum:


2

İstediğinizi yapabilen birkaç hazır konsol yardımcı programı olsa da, Python gibi genel amaçlı bir programlama dilinde kolayca genişletebileceğiniz ve uyum sağlayabileceğiniz birkaç satır kod yazmak muhtemelen daha az zaman alacaktır. ihtiyaçlarınızı.

lxmlAyrıştırma için kullanılan bir python betiği - bir dosyanın veya URL'nin adını ilk parametre olarak, bir XPath ifadesini ikinci parametre olarak alır ve verilen ifadeyle eşleşen dizeleri / düğümleri yazdırır.

örnek 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))

lxmlile kurulabilir pip install lxml. Ubuntu üzerinde kullanabilirsiniz sudo apt install python-lxml.

kullanım

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

lxml URL'yi giriş olarak da kabul eder:

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

Not : XML'nizde önek içermeyen varsayılan bir ad alanı varsa (örneğin xmlns=http://abc...) p, ifadelerinizdeki önekleri ('hack' tarafından sağlanan) kullanmanız gerekir; örneğin //p:module, modülleri bir pom.xmldosyadan almak için . Ön pek XML'nizde zaten eşlenmişse, başka bir önek kullanmak için komut dosyasını değiştirmeniz gerekir.


ÖRNEK 2

Bir apache maven dosyasından modül adlarını çıkarmak için dar bir amaca hizmet eden tek seferlik bir komut dosyası. Düğüm adının ( module) varsayılan ad alanı ile nasıl ön ekine sahip olduğuna dikkat edin {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)

Bu, ekstra paketler kurmaktan kaçınmak istediğinizde veya erişiminiz olmadığında harika. Bir yapı makinesinde, fazladan bir pip installfazlalığı apt-getveya yumçağrıyı haklı çıkarabilirim . Teşekkürler!
E. Moffat

0

Yuzem'in yöntemi , işlevdeki ve değişken atamalarındaki <ve >işaretlerinin sırasını tersine çevirerek geliştirilebilir rdom, böylece:

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

dönüşür:

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

Ayrıştırma bu şekilde yapılmazsa, XML dosyasındaki son etikete asla ulaşılmaz. whileDöngünün sonunda başka bir XML dosyası çıktısını almak istiyorsanız bu sorunlu olabilir .


0

XML nitelikleri istiyorsanız bu çalışır:

$ 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

"Asla XML, JSON ... düzgün bir araç olmadan bash gelen ayrıştırmak" gibi görünüyor olsa da, ben tavsiye etmiyorum. Eğer bu yan iş ise, uygun aracı aramak çok zordur, sonra öğrenin ... Awk dakikalar içinde yapabilir. Programlarımın yukarıda belirtilen tüm veriler ve daha fazla veri üzerinde çalışması gerekiyor. Cehennem, problemi dakikalar içinde uyandırabilirsem 5-7-10 farklı formatı ayrıştırmak için 30 aracı test etmek istemiyorum. XML, JSON ya da her neyse umrumda değil! Hepsi için tek bir çözüme ihtiyacım var.

Örnek olarak: SmartHome programım evlerimizi işletiyor. Bunu yaparken, kontrol edemediğim çok farklı formatlarda çok sayıda veri okur. İhtiyacım olan verileri okumak için birkaç dakikadan fazla zaman harcamak istemediğim için asla özel ve uygun araçlar kullanmıyorum. FS ve RS ayarlamaları ile bu awk çözümü herhangi bir metin formatı için mükemmel çalışır. Ancak, birincil göreviniz öncelikle bu formattaki bir sürü veriyle çalışmak uygun bir cevap olmayabilir!

Dün karşılaştığım bash'den XML ayrıştırma sorunu. Herhangi bir hiyerarşik veri biçimi için bunu nasıl yapacağım. Bir bonus olarak - doğrudan bir bash betiğindeki değişkenlere veri atarım.

İncileri okumayı kolaylaştırmak için aşamalı olarak çözüm sunacağım. OP test verilerinden bir dosya oluşturdum: test.xml

Bahsedilen XML'yi bash'de ayrıştırma ve verileri 90 karakterde çıkarma:

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

Normalde daha okunabilir sürümü kullanıyorum çünkü gerçek hayatta değişmek daha kolay olduğu için sıklıkla farklı testler yapmam gerekiyor:

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

Formatın nasıl adlandırıldığı umurumda değil. Sadece en basit çözümü arıyorum. Bu özel durumda, verilerden yeni satırın kayıt ayırıcı (RS) ve <> sınırlama alanları (FS) olduğunu görebiliyorum. Orijinal durumumda, iki kayıt içinde 6 değerin karmaşık indekslemesini yaptım, bunlarla ilgili olarak, verinin ne zaman mevcut olduğunu ve ayrıca alanların (kayıtların) bulunup bulunmayabileceğini buldum. Sorunu mükemmel bir şekilde çözmek için 4 satır awk aldı. Bu yüzden, fikri kullanmadan önce her ihtiyaca uyarlayın!

İkinci kısım, bir satırda (RS) istenen dize olduğunu ve gerekli alanları (FS) yazdırdığını görünüyor. Yukarıdakiler, bu şekilde kullandığım son komuttan (4 kat daha uzun) kopyalayıp uyarlamak için yaklaşık 30 saniye sürdü. Ve işte bu! 90 karakterle tamamlandı.

Ancak, verileri her zaman komut dosyamdaki değişkenlere düzgün bir şekilde almam gerekiyor. İlk önce yapıları şu şekilde test ediyorum:

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

Bazı durumlarda print yerine printf kullanıyorum. Her şeyin iyi göründüğünü gördüğümde, değişkenlere değer atamayı bitiriyorum. Birçoğunun "eval" in "kötü" olduğunu düşündüğünü biliyorum, yorum yapmaya gerek yok :) Trick yıllarca ağlarımın her birinde mükemmel çalışıyor. Ancak bunun neden kötü bir uygulama olabileceğini anlamadıysanız öğrenmeye devam edin! Bas değişken atamaları ve geniş boşluklar dahil, çözümüm her şeyi yapmak için 120 karaktere ihtiyaç duyuyor.

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"
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.