วิธีแยกวิเคราะห์ XML ใน Bash


140

ตามหลักการแล้วสิ่งที่ฉันต้องการจะทำได้คือ:

cat xhtmlfile.xhtml |
getElementViaXPath --path='/html/head/title' |
sed -e 's%(^<title>|</title>$)%%g' > titleOfXHTMLPage.txt

คำตอบ:


156

นี่เป็นเพียงคำอธิบายของคำตอบของ Yuzemแต่ฉันไม่รู้สึกว่าควรแก้ไขกับคนอื่นมากขนาดนี้และความคิดเห็นไม่อนุญาตให้จัดรูปแบบดังนั้น ...

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 เป็นสองฟิลด์ 'tag' และ 'value' อ่านแล้วกำหนดตัวแปรที่ชอบ: และ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>item-apple-iso@2x.png</Key>
    <LastModified>2011-07-25T22:23:04.000Z</LastModified>
    <ETag>&quot;0032a28286680abee71aed5d059c6a09&quot;</ETag>
    <Size>1785</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
</ListBucketResult>

และลูปต่อไปนี้:

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

คุณควรได้รับ:

 => 
ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/" => 
Name => sth-items
/Name => 
IsTruncated => false
/IsTruncated => 
Contents => 
Key => item-apple-iso@2x.png
/Key => 
LastModified => 2011-07-25T22:23:04.000Z
/LastModified => 
ETag => &quot;0032a28286680abee71aed5d059c6a09&quot;
/ETag => 
Size => 1785
/Size => 
StorageClass => STANDARD
/StorageClass => 
/Contents => 

ดังนั้นถ้าเราเขียนwhileวนซ้ำเหมือนของ Yuzem:

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
}

ฉันไม่เห็นเหตุผลใด ๆ ว่าทำไมถึงไม่ได้ผล


2
หากคุณสร้าง IFS (ตัวคั่นฟิลด์อินพุต) แบบโกลบอลคุณควรรีเซ็ตกลับเป็นค่าเดิมในตอนท้ายฉันแก้ไขคำตอบเพื่อให้มีสิ่งนั้น มิฉะนั้นการแยกอินพุตอื่น ๆ ที่คุณทำในภายหลังในสคริปต์ของคุณจะยุ่งเหยิง ฉันสงสัยว่าสาเหตุที่ local ไม่ได้ผลสำหรับคุณเป็นเพราะคุณใช้ bash ในโหมดความเข้ากันได้ (เช่น shbang ของคุณคือ #! / bin / sh) หรือเป็น bash เวอร์ชันโบราณ
ชาด

30
เพียงเพราะคุณสามารถเขียน parser ของคุณเองได้ไม่ได้หมายความว่าคุณควร
Stephen Niedzielski

1
@chad แน่นอนมันพูดอะไรบางอย่างเกี่ยวกับ AWS' ขั้นตอนการทำงาน / การดำเนินงานที่ผมค้นหาคำตอบที่ 'ทุบตี XML' ไปยังwgetเนื้อหาของถัง S3!
Alastair

2
@Alastair ดูgithub.com/chad3814/s3scriptsสำหรับชุดของ bash scripts ที่เราใช้จัดการวัตถุ S3
ชาด

5
การกำหนด IFS ในตัวแปรโลคัลนั้นเปราะบางและไม่จำเป็น เพียงแค่ทำ: IFS=\< read ...ซึ่งจะตั้งค่า IFS สำหรับการเรียกอ่านเท่านั้น (โปรดทราบว่าฉันไม่ได้รับรองการฝึกฝนการใช้readเพื่อแยกวิเคราะห์ xml และฉันเชื่อว่าการทำเช่นนั้นเต็มไปด้วยอันตรายและควรหลีกเลี่ยง)
William Pursell

65

คุณสามารถทำได้อย่างง่ายดายโดยใช้เพียงทุบตี คุณต้องเพิ่มฟังก์ชันนี้เท่านั้น:

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

คุณช่วยอธิบายให้ละเอียดได้ไหม ฉันพนันได้เลยว่ามันชัดเจนมากสำหรับคุณ .. และนี่อาจเป็นคำตอบที่ดี - ถ้าฉันสามารถบอกได้ว่าคุณกำลังทำอะไรอยู่ที่นั่น .. คุณสามารถแยกมันออกมาอีกหน่อยอาจจะสร้างผลลัพธ์ตัวอย่างได้หรือไม่?
Alex Gray

2
ให้เครดิตกับต้นฉบับ - ซับในตัวนี้ดูหรูหราและน่าทึ่งมาก
maverick

2
แฮ็คที่ยอดเยี่ยม แต่ฉันต้องใช้เครื่องหมายคำพูดคู่เช่น echo "$ C" เพื่อป้องกันการขยายเชลล์และการตีความบรรทัดท้ายที่ถูกต้อง (ขึ้นอยู่กับการเข้ารหัส)
user311174

8
แยก XML ที่มี grep และ awk ไม่เป็นไร อาจเป็นการประนีประนอมที่ยอมรับได้หาก XML นั้นเรียบง่ายเพียงพอและคุณมีเวลาไม่มากนัก แต่ก็ไม่สามารถเรียกได้ว่าเป็นทางออกที่ดีเลยทีเดียว
peterh - คืนสถานะ Monica

61

เครื่องมือบรรทัดคำสั่งที่สามารถเรียกได้จากเชลล์สคริปต์ ได้แก่ :

  • 4xpath - เครื่องห่อบรรทัดคำสั่งรอบแพ็คเกจ4Suiteของ Python

  • XMLStarlet

  • xpath - wrapper บรรทัดคำสั่งรอบ ๆ ไลบรารี XPath ของ Perl

    sudo apt-get install libxml-xpath-perl
    
  • Xidel - ทำงานร่วมกับ URL และไฟล์ ยังใช้งานได้กับ JSON

ฉันยังใช้ xmllint และ xsltproc กับสคริปต์แปลง XSL เล็กน้อยเพื่อทำการประมวลผล XML จากบรรทัดคำสั่งหรือในเชลล์สคริปต์


2
ฉันจะดาวน์โหลด 'xpath' หรือ '4xpath' ได้จากที่ไหน?
Opher

3
ใช่โหวต / คำขอครั้งที่สอง - จะดาวน์โหลดเครื่องมือเหล่านั้นได้ที่ไหนหรือคุณหมายถึงต้องเขียน wrapper ด้วยตนเอง? ฉันไม่อยากเสียเวลาทำอย่างนั้นเว้นแต่จำเป็น
David

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

22

คุณสามารถใช้ยูทิลิตี้ xpath ติดตั้งด้วยแพ็คเกจ Perl XML-XPath

การใช้งาน:

/usr/bin/xpath [filename] query

หรือXMLStarlet ในการติดตั้งบน opensuse ให้ใช้:

sudo zypper install xmlstarlet

หรือลองcnf xmlใช้แพลตฟอร์มอื่น ๆ


5
การใช้ xml starlet เป็นตัวเลือกที่ดีกว่าการเขียน serializer ของตัวเอง (ตามที่แนะนำในคำตอบอื่น ๆ )
Bruno von Paris

ในหลาย ๆ ระบบระบบxpathที่ติดตั้งไว้ล่วงหน้าไม่เหมาะสำหรับใช้เป็นส่วนประกอบในสคริปต์ ดูเช่นstackoverflow.com/questions/15461737/...สำหรับรายละเอียดเพิ่มเติม
tripleee

2
บน Ubuntu / Debianapt-get install xmlstarlet
rubo77


6

ตรวจสอบXML2จากhttp://www.ofb.net/~egnor/xml2/ซึ่งแปลง XML เป็นรูปแบบเชิงเส้น


เครื่องมือที่มีประโยชน์มาก ลิงก์เสีย (ดูweb.archive.org/web/20160312110413/https://dan.egnor.name/xml2 ) แต่มีโคลนที่ใช้งานได้และถูกแช่แข็งบน github: github.com/clone/xml2
Joshua Goldberg

5

เริ่มต้นจากคำตอบของ chad นี่คือโซลูชันการทำงานที่สมบูรณ์ในการแยกวิเคราะห์ UML ด้วยการจัดการความคิดเห็นโดยใช้ฟังก์ชันเล็ก ๆ น้อย ๆ เพียง 2 ฟังก์ชั่น (มากกว่า 2 bu คุณสามารถผสมได้ทั้งหมด) ฉันไม่ได้บอกว่า chad ไม่ได้ผลเลย แต่มันมีปัญหามากเกินไปกับไฟล์ XML ที่มีรูปแบบไม่ดีดังนั้นคุณต้องยุ่งยากกว่านี้อีกหน่อยในการจัดการความคิดเห็นและช่องว่างที่ใส่ผิด / CR / TAB / ฯลฯ

จุดประสงค์ของคำตอบนี้คือเพื่อมอบฟังก์ชันทุบตีแบบสำเร็จรูปให้กับทุกคนที่ต้องการการแยกวิเคราะห์ UML โดยไม่ต้องใช้เครื่องมือที่ซับซ้อนโดยใช้ perl, python หรือสิ่งอื่นใด สำหรับฉันฉันไม่สามารถติดตั้ง cpan หรือโมดูล perl สำหรับระบบปฏิบัติการการผลิตเก่าที่ฉันกำลังทำงานอยู่และ python ไม่พร้อมใช้งาน

อันดับแรกคำจำกัดความของคำ UML ที่ใช้ในโพสต์นี้:

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

แก้ไข: ฟังก์ชั่นที่อัปเดตพร้อมที่จับของ:

  • Websphere xml (แอตทริบิวต์ xmi และ xmlns)
  • ต้องมีเทอร์มินัลที่เข้ากันได้กับ 256 สี
  • 24 เฉดสีเทา
  • เพิ่มความเข้ากันได้สำหรับ IBM AIX bash 3.2.16 (1)

ฟังก์ชันอันดับแรกคือ xml_read_dom ซึ่งเรียกซ้ำโดย xml_read:

xml_read_dom() {
# /programming/893585/how-to-parse-xml-in-bash
local ENTITY IFS=\>
if $ITSACOMMENT; then
  read -d \< COMMENTS
  COMMENTS="$(rtrim "${COMMENTS}")"
  return 0
else
  read -d \< ENTITY CONTENT
  CR=$?
  [ "x${ENTITY:0:1}x" == "x/x" ] && return 0
  TAG_NAME=${ENTITY%%[[:space:]]*}
  [ "x${TAG_NAME}x" == "x?xmlx" ] && TAG_NAME=xml
  TAG_NAME=${TAG_NAME%%:*}
  ATTRIBUTES=${ENTITY#*[[:space:]]}
  ATTRIBUTES="${ATTRIBUTES//xmi:/}"
  ATTRIBUTES="${ATTRIBUTES//xmlns:/}"
fi

# when comments sticks to !-- :
[ "x${TAG_NAME:0:3}x" == "x!--x" ] && COMMENTS="${TAG_NAME:3} ${ATTRIBUTES}" && ITSACOMMENT=true && return 0

# http://tldp.org/LDP/abs/html/string-manipulation.html
# INFO: oh wait it doesn't work on IBM AIX bash 3.2.16(1):
# [ "x${ATTRIBUTES:(-1):1}x" == "x/x" -o "x${ATTRIBUTES:(-1):1}x" == "x?x" ] && ATTRIBUTES="${ATTRIBUTES:0:(-1)}"
[ "x${ATTRIBUTES:${#ATTRIBUTES} -1:1}x" == "x/x" -o "x${ATTRIBUTES:${#ATTRIBUTES} -1:1}x" == "x?x" ] && ATTRIBUTES="${ATTRIBUTES:0:${#ATTRIBUTES} -1}"
return $CR
}

และอันที่สอง:

xml_read() {
# /programming/893585/how-to-parse-xml-in-bash
ITSACOMMENT=false
local MULTIPLE_ATTR LIGHT FORCE_PRINT XAPPLY XCOMMAND XATTRIBUTE GETCONTENT fileXml tag attributes attribute tag2print TAGPRINTED attribute2print XAPPLIED_COLOR PROSTPROCESS USAGE
local TMP LOG LOGG
LIGHT=false
FORCE_PRINT=false
XAPPLY=false
MULTIPLE_ATTR=false
XAPPLIED_COLOR=g
TAGPRINTED=false
GETCONTENT=false
PROSTPROCESS=cat
Debug=${Debug:-false}
TMP=/tmp/xml_read.$RANDOM
USAGE="${C}${FUNCNAME}${c} [-cdlp] [-x command <-a attribute>] <file.xml> [tag | \"any\"] [attributes .. | \"content\"]
${nn[2]}  -c = NOCOLOR${END}
${nn[2]}  -d = Debug${END}
${nn[2]}  -l = LIGHT (no \"attribute=\" printed)${END}
${nn[2]}  -p = FORCE PRINT (when no attributes given)${END}
${nn[2]}  -x = apply a command on an attribute and print the result instead of the former value, in green color${END}
${nn[1]}  (no attribute given will load their values into your shell; use '-p' to print them as well)${END}"

! (($#)) && echo2 "$USAGE" && return 99
(( $# < 2 )) && ERROR nbaram 2 0 && return 99
# getopts:
while getopts :cdlpx:a: _OPT 2>/dev/null
do
{
  case ${_OPT} in
    c) PROSTPROCESS="${DECOLORIZE}" ;;
    d) local Debug=true ;;
    l) LIGHT=true; XAPPLIED_COLOR=END ;;
    p) FORCE_PRINT=true ;;
    x) XAPPLY=true; XCOMMAND="${OPTARG}" ;;
    a) XATTRIBUTE="${OPTARG}" ;;
    *) _NOARGS="${_NOARGS}${_NOARGS+, }-${OPTARG}" ;;
  esac
}
done
shift $((OPTIND - 1))
unset _OPT OPTARG OPTIND
[ "X${_NOARGS}" != "X" ] && ERROR param "${_NOARGS}" 0

fileXml=$1
tag=$2
(( $# > 2 )) && shift 2 && attributes=$*
(( $# > 1 )) && MULTIPLE_ATTR=true

[ -d "${fileXml}" -o ! -s "${fileXml}" ] && ERROR empty "${fileXml}" 0 && return 1
$XAPPLY && $MULTIPLE_ATTR && [ -z "${XATTRIBUTE}" ] && ERROR param "-x command " 0 && return 2
# nb attributes == 1 because $MULTIPLE_ATTR is false
[ "${attributes}" == "content" ] && GETCONTENT=true

while xml_read_dom; do
  # (( CR != 0 )) && break
  (( PIPESTATUS[1] != 0 )) && break

  if $ITSACOMMENT; then
    # oh wait it doesn't work on IBM AIX bash 3.2.16(1):
    # if [ "x${COMMENTS:(-2):2}x" == "x--x" ]; then COMMENTS="${COMMENTS:0:(-2)}" && ITSACOMMENT=false
    # elif [ "x${COMMENTS:(-3):3}x" == "x-->x" ]; then COMMENTS="${COMMENTS:0:(-3)}" && ITSACOMMENT=false
    if [ "x${COMMENTS:${#COMMENTS} - 2:2}x" == "x--x" ]; then COMMENTS="${COMMENTS:0:${#COMMENTS} - 2}" && ITSACOMMENT=false
    elif [ "x${COMMENTS:${#COMMENTS} - 3:3}x" == "x-->x" ]; then COMMENTS="${COMMENTS:0:${#COMMENTS} - 3}" && ITSACOMMENT=false
    fi
    $Debug && echo2 "${N}${COMMENTS}${END}"
  elif test "${TAG_NAME}"; then
    if [ "x${TAG_NAME}x" == "x${tag}x" -o "x${tag}x" == "xanyx" ]; then
      if $GETCONTENT; then
        CONTENT="$(trim "${CONTENT}")"
        test ${CONTENT} && echo "${CONTENT}"
      else
        # eval local $ATTRIBUTES => eval test "\"\$${attribute}\"" will be true for matching attributes
        eval local $ATTRIBUTES
        $Debug && (echo2 "${m}${TAG_NAME}: ${M}$ATTRIBUTES${END}"; test ${CONTENT} && echo2 "${m}CONTENT=${M}$CONTENT${END}")
        if test "${attributes}"; then
          if $MULTIPLE_ATTR; then
            # we don't print "tag: attr=x ..." for a tag passed as argument: it's usefull only for "any" tags so then we print the matching tags found
            ! $LIGHT && [ "x${tag}x" == "xanyx" ] && tag2print="${g6}${TAG_NAME}: "
            for attribute in ${attributes}; do
              ! $LIGHT && attribute2print="${g10}${attribute}${g6}=${g14}"
              if eval test "\"\$${attribute}\""; then
                test "${tag2print}" && ${print} "${tag2print}"
                TAGPRINTED=true; unset tag2print
                if [ "$XAPPLY" == "true" -a "${attribute}" == "${XATTRIBUTE}" ]; then
                  eval ${print} "%s%s\ " "\${attribute2print}" "\${${XAPPLIED_COLOR}}\"\$(\$XCOMMAND \$${attribute})\"\${END}" && eval unset ${attribute}
                else
                  eval ${print} "%s%s\ " "\${attribute2print}" "\"\$${attribute}\"" && eval unset ${attribute}
                fi
              fi
            done
            # this trick prints a CR only if attributes have been printed durint the loop:
            $TAGPRINTED && ${print} "\n" && TAGPRINTED=false
          else
            if eval test "\"\$${attributes}\""; then
              if $XAPPLY; then
                eval echo "\${g}\$(\$XCOMMAND \$${attributes})" && eval unset ${attributes}
              else
                eval echo "\$${attributes}" && eval unset ${attributes}
              fi
            fi
          fi
        else
          echo eval $ATTRIBUTES >>$TMP
        fi
      fi
    fi
  fi
  unset CR TAG_NAME ATTRIBUTES CONTENT COMMENTS
done < "${fileXml}" | ${PROSTPROCESS}
# http://mywiki.wooledge.org/BashFAQ/024
# INFO: I set variables in a "while loop" that's in a pipeline. Why do they disappear? workaround:
if [ -s "$TMP" ]; then
  $FORCE_PRINT && ! $LIGHT && cat $TMP
  # $FORCE_PRINT && $LIGHT && perl -pe 's/[[:space:]].*?=/ /g' $TMP
  $FORCE_PRINT && $LIGHT && sed -r 's/[^\"]*([\"][^\"]*[\"][,]?)[^\"]*/\1 /g' $TMP
  . $TMP
  rm -f $TMP
fi
unset ITSACOMMENT
}

และสุดท้ายฟังก์ชัน rtrim, trim และ echo2 (เป็น stderr):

rtrim() {
local var=$@
var="${var%"${var##*[![:space:]]}"}"   # remove trailing whitespace characters
echo -n "$var"
}
trim() {
local var=$@
var="${var#"${var%%[![:space:]]*}"}"   # remove leading whitespace characters
var="${var%"${var##*[![:space:]]}"}"   # remove trailing whitespace characters
echo -n "$var"
}
echo2() { echo -e "$@" 1>&2; }

การระบายสี:

โอ้และคุณจะต้องมีการกำหนดตัวแปรไดนามิกการระบายสีที่เป็นระเบียบในตอนแรกและส่งออกด้วย:

set -a
TERM=xterm-256color
case ${UNAME} in
AIX|SunOS)
  M=$(${print} '\033[1;35m')
  m=$(${print} '\033[0;35m')
  END=$(${print} '\033[0m')
;;
*)
  m=$(tput setaf 5)
  M=$(tput setaf 13)
  # END=$(tput sgr0)          # issue on Linux: it can produces ^[(B instead of ^[[0m, more likely when using screenrc
  END=$(${print} '\033[0m')
;;
esac
# 24 shades of grey:
for i in $(seq 0 23); do eval g$i="$(${print} \"\\033\[38\;5\;$((232 + i))m\")" ; done
# another way of having an array of 5 shades of grey:
declare -a colorNums=(238 240 243 248 254)
for num in 0 1 2 3 4; do nn[$num]=$(${print} "\033[38;5;${colorNums[$num]}m"); NN[$num]=$(${print} "\033[48;5;${colorNums[$num]}m"); done
# piped decolorization:
DECOLORIZE='eval sed "s,${END}\[[0-9;]*[m|K],,g"'

วิธีโหลดทุกอย่าง:

ไม่ว่าคุณจะรู้วิธีสร้างฟังก์ชั่นและโหลดผ่าน FPATH (ksh) หรือการจำลอง FPATH (bash)

มิฉะนั้นให้คัดลอก / วางทุกอย่างในบรรทัดคำสั่ง

มันทำงานอย่างไร:

xml_read [-cdlp] [-x command <-a attribute>] <file.xml> [tag | "any"] [attributes .. | "content"]
  -c = NOCOLOR
  -d = Debug
  -l = LIGHT (no \"attribute=\" printed)
  -p = FORCE PRINT (when no attributes given)
  -x = apply a command on an attribute and print the result instead of the former value, in green color
  (no attribute given will load their values into your shell as $ATTRIBUTE=value; use '-p' to print them as well)

xml_read server.xml title content     # print content between <title></title>
xml_read server.xml Connector port    # print all port values from Connector tags
xml_read server.xml any port          # print all port values from any tags

ด้วยความคิดเห็นในโหมดดีบัก (-d) และแอตทริบิวต์ที่แยกวิเคราะห์จะถูกพิมพ์ไปยัง stderr


ฉันกำลังพยายามใช้สองฟังก์ชันข้างต้นซึ่งก่อให้เกิดสิ่งต่อไปนี้: ./read_xml.sh: line 22: (-1): substring expression < 0?
khmarbaise

บรรทัดที่ 22:[ "x${ATTRIBUTES:(-1):1}x" == "x?x" ] ...
khmarbaise

ขอโทษ khmarbaise นี่คือฟังก์ชัน bash shell หากคุณต้องการดัดแปลงเป็นเชลล์สคริปต์คุณต้องคาดหวังการดัดแปลงเล็กน้อยอย่างแน่นอน! นอกจากนี้ฟังก์ชั่นที่อัปเดตยังจัดการข้อผิดพลาดของคุณด้วย)
เก็บขยะ

4

ฉันไม่ทราบเกี่ยวกับเครื่องมือแยกวิเคราะห์เชลล์ XML ทั้งหมด ดังนั้นคุณมักจะต้องใช้เครื่องมือที่เขียนด้วยภาษาอื่น

My XML :: โมดูล Twig Perl มาพร้อมกับเครื่องมือดังกล่าว: xml_grepซึ่งคุณอาจจะเขียนสิ่งที่คุณต้องการเป็นxml_grep -t '/html/head/title' xhtmlfile.xhtml > titleOfXHTMLPage.txt( -tตัวเลือกให้ผลลัพธ์เป็นข้อความแทน xml)


4

เครื่องมือบรรทัดคำสั่งก็คือใหม่ของฉันXidel นอกจากนี้ยังรองรับ XPath 2 และ XQuery ตรงกันข้ามกับ xpath / xmlstarlet ที่กล่าวถึงแล้ว

ชื่อเรื่องสามารถอ่านได้ดังนี้:

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

และยังมีคุณสมบัติที่ยอดเยี่ยมในการส่งออกตัวแปรหลายตัวเพื่อทุบตี ตัวอย่างเช่น

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

ตั้งค่า$titleเป็นชื่อเรื่องและ$imgcountจำนวนภาพในไฟล์ซึ่งควรยืดหยุ่นพอ ๆ กับการแยกวิเคราะห์โดยตรงใน bash



2

หลังจากการวิจัยเกี่ยวกับการแปลระหว่างรูปแบบ Linux และ Windows ของเส้นทางไฟล์ในไฟล์ XML ฉันพบบทแนะนำและวิธีแก้ปัญหาที่น่าสนใจเกี่ยวกับ:


2

แม้ว่าจะมียูทิลิตีคอนโซลสำเร็จรูปจำนวนไม่น้อยที่สามารถทำในสิ่งที่คุณต้องการได้ แต่อาจใช้เวลาน้อยกว่าในการเขียนโค้ดสองสามบรรทัดในภาษาโปรแกรมสำหรับวัตถุประสงค์ทั่วไปเช่น Python ซึ่งคุณสามารถขยายและปรับให้เข้ากับได้อย่างง่ายดาย ความต้องการของคุณ

นี่คือสคริปต์ python ที่ใช้lxmlสำหรับการแยกวิเคราะห์โดยใช้ชื่อไฟล์หรือ URL เป็นพารามิเตอร์แรกนิพจน์ XPath เป็นพารามิเตอร์ที่สองและพิมพ์สตริง / โหนดที่ตรงกับนิพจน์ที่กำหนด

ตัวอย่าง 1

#!/usr/bin/env python
import sys
from lxml import etree

tree = etree.parse(sys.argv[1])
xpath_expression = sys.argv[2]

#  a hack allowing to access the
#  default namespace (if defined) via the 'p:' prefix    
#  E.g. given a default namespaces such as 'xmlns="http://maven.apache.org/POM/4.0.0"'
#  an XPath of '//p:module' will return all the 'module' nodes
ns = tree.getroot().nsmap
if ns.keys() and None in ns:
    ns['p'] = ns.pop(None)
#   end of hack    

for e in tree.xpath(xpath_expression, namespaces=ns):
    if isinstance(e, str):
        print(e)
    else:
        print(e.text and e.text.strip() or etree.tostring(e, pretty_print=True))

lxmlสามารถติดตั้งด้วยpip install lxml. บน Ubuntu คุณสามารถใช้sudo apt install python-lxml.

การใช้งาน

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

lxml ยังยอมรับ URL เป็นอินพุต:

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

หมายเหตุ : หาก XML ของคุณมีเนมสเปซเริ่มต้นที่ไม่มีคำนำหน้า (เช่นxmlns=http://abc...) คุณจะต้องใช้pคำนำหน้า (ให้โดย 'แฮ็ก') ในนิพจน์ของคุณเช่น//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)

สิ่งนี้ยอดเยี่ยมเมื่อคุณต้องการหลีกเลี่ยงการติดตั้งแพ็คเกจเพิ่มเติมหรือไม่สามารถเข้าถึงได้ ในการสร้างเครื่องผมสามารถปรับเป็นพิเศษpip installกว่าapt-getหรือyumโทร ขอบคุณ!
E. Moffat

0

วิธีการของ Yuzem สามารถปรับปรุงได้โดยการกลับลำดับของเครื่องหมาย<และ>ในrdomฟังก์ชันและการกำหนดตัวแปรเพื่อให้:

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

กลายเป็น:

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

หากไม่ได้ทำการแยกวิเคราะห์เช่นนี้แท็กสุดท้ายในไฟล์ XML จะไม่ถึง สิ่งนี้อาจเป็นปัญหาได้หากคุณตั้งใจจะส่งออกไฟล์ XML อื่นที่ท้ายwhileลูป


0

ใช้งานได้หากคุณต้องการแอตทริบิวต์ XML:

$ cat alfa.xml
<video server="asdf.com" stream="H264_400.mp4" cdn="limelight"/>

$ sed 's.[^ ]*..;s./>..' alfa.xml > alfa.sh

$ . ./alfa.sh

$ echo "$stream"
H264_400.mp4

0

แม้ว่า "ไม่เคยแยกวิเคราะห์ XML, JSON ... จากการทุบตีโดยไม่ใช้เครื่องมือที่เหมาะสม" เป็นคำแนะนำที่ดี แต่ฉันไม่เห็นด้วย หากนี่เป็นงานด้านมันเป็นเรื่องยากที่จะมองหาเครื่องมือที่เหมาะสมแล้วเรียนรู้ ... Awk ทำได้ในไม่กี่นาที โปรแกรมของฉันต้องทำงานกับข้อมูลที่กล่าวมาทั้งหมดและข้อมูลอื่น ๆ อีกมากมาย นรกฉันไม่ต้องการทดสอบเครื่องมือ 30 รายการเพื่อแยกวิเคราะห์รูปแบบต่างๆ 5-7-10 รูปแบบที่ฉันต้องการหากฉันสามารถแก้ไขปัญหาได้ในไม่กี่นาที ฉันไม่สนใจ XML, JSON หรืออะไรก็ตาม! ฉันต้องการโซลูชันเดียวสำหรับพวกเขาทั้งหมด

ดังตัวอย่าง: โปรแกรม SmartHome ของฉันทำงานในบ้านของเรา ในขณะที่ทำมันอ่านข้อมูลมากมายเหลือเฟือในรูปแบบต่างๆมากเกินไปที่ฉันไม่สามารถควบคุมได้ ฉันไม่เคยใช้เครื่องมือเฉพาะที่เหมาะสมเพราะฉันไม่ต้องการใช้เวลามากกว่านาทีในการอ่านข้อมูลที่ฉันต้องการ ด้วยการปรับ FS และ RS โซลูชัน awk นี้ทำงานได้อย่างสมบูรณ์แบบสำหรับรูปแบบข้อความใด ๆ แต่อาจไม่ใช่คำตอบที่เหมาะสมเมื่องานหลักของคุณคือการทำงานกับข้อมูลจำนวนมากในรูปแบบนั้นเป็นหลัก!

ปัญหาในการแยกวิเคราะห์ XML จาก bash ที่ฉันเผชิญเมื่อวานนี้ นี่คือวิธีที่ฉันทำกับรูปแบบข้อมูลลำดับชั้นใด ๆ เป็นโบนัส - ฉันกำหนดข้อมูลให้กับตัวแปรในสคริปต์ทุบตีโดยตรง

เพื่อให้อ่านง่ายขึ้นฉันจะนำเสนอวิธีแก้ปัญหาเป็นขั้นตอน จากข้อมูลการทดสอบ OP ฉันสร้างไฟล์: test.xml

การแยกวิเคราะห์ XML ดังกล่าวในการทุบตีและดึงข้อมูลใน 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 ค่าที่ซับซ้อนภายในสองระเบียนเกี่ยวข้องกับค่าเหล่านี้ค้นหาเมื่อข้อมูลมีอยู่รวมทั้งเขตข้อมูล (ระเบียน) อาจมีหรือไม่มีอยู่ ใช้ awk 4 บรรทัดเพื่อแก้ปัญหาอย่างสมบูรณ์แบบ ดังนั้นปรับความคิดให้เข้ากับแต่ละความต้องการก่อนใช้!

ส่วนที่สองเพียงแค่ดูว่ามีสตริงที่ต้องการในบรรทัด (RS) และถ้าเป็นเช่นนั้นให้พิมพ์ฟิลด์ที่จำเป็น (FS) ข้างต้นใช้เวลาประมาณ 30 วินาทีในการคัดลอกและปรับเปลี่ยนจากคำสั่งสุดท้ายที่ฉันใช้วิธีนี้ (นานกว่า 4 เท่า) และนั่นมัน! ทำได้ใน 90 ตัวอักษร

แต่ฉันจำเป็นต้องรับข้อมูลอย่างเรียบร้อยเป็นตัวแปรในสคริปต์ของฉัน ก่อนอื่นฉันทดสอบโครงสร้างดังนี้:

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

ในบางกรณีฉันใช้ printf แทนการพิมพ์ เมื่อฉันเห็นว่าทุกอย่างดูดีฉันก็กำหนดค่าให้กับตัวแปรให้เสร็จสิ้น ฉันรู้ว่าหลายคนคิดว่า "eval" เป็น "ความชั่วร้าย" ไม่จำเป็นต้องแสดงความคิดเห็น :) Trick ทำงานได้อย่างสมบูรณ์บนเครือข่ายทั้งสี่ของฉันมาหลายปีแล้ว แต่ให้เรียนรู้หากคุณไม่เข้าใจว่าทำไมถึงเป็นการปฏิบัติที่ไม่ดี! รวมถึงการกำหนดตัวแปร 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"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.