Làm cách nào để phân tích cú pháp XML trong Bash?


Câu trả lời:


152

Đây thực sự chỉ là một lời giải thích cho câu trả lời của Yuzem , nhưng tôi không cảm thấy việc chỉnh sửa này nên được thực hiện cho người khác và các bình luận không cho phép định dạng, vì vậy ...

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

Hãy gọi đó là "read_dom" thay vì "rdom", bỏ nó ra một chút và sử dụng các biến dài hơn:

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

Được rồi để nó định nghĩa một hàm gọi là read_dom. Dòng đầu tiên làm cho IFS (dấu tách trường đầu vào) cục bộ thành hàm này và thay đổi nó thành>. Điều đó có nghĩa là khi bạn đọc dữ liệu thay vì tự động được phân chia trên không gian, tab hoặc dòng mới, nó sẽ được phân chia trên '>'. Dòng tiếp theo nói để đọc đầu vào từ stdin và thay vì dừng ở một dòng mới, hãy dừng lại khi bạn thấy ký tự '<' (cờ -d cho cờ phân cách). Những gì được đọc sau đó được phân tách bằng IFS và được gán cho biến ENTITY và NỘI DUNG. Vì vậy, hãy làm như sau:

<tag>value</tag>

Cuộc gọi đầu tiên để read_domnhận một chuỗi trống (vì '<' là ký tự đầu tiên). Điều đó bị IFS chia thành '', vì không có ký tự '>'. Đọc sau đó gán một chuỗi rỗng cho cả hai biến. Cuộc gọi thứ hai nhận được chuỗi 'tag> value'. Điều đó được phân chia sau đó bởi IFS thành hai trường 'tag' và 'value'. Đọc sau đó gán các biến như: ENTITY=tagCONTENT=value. Cuộc gọi thứ ba nhận được chuỗi '/ tag>'. Điều đó được phân chia bởi IFS thành hai trường '/ tag' và ''. Đọc sau đó gán các biến như: ENTITY=/tagCONTENT=. Cuộc gọi thứ tư sẽ trả về trạng thái khác không vì chúng tôi đã kết thúc tập tin.

Bây giờ vòng lặp while của anh đã dọn sạch một chút để khớp với điều trên:

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

Dòng đầu tiên chỉ nói, "trong khi hàm read_dom chuyển trạng thái 0, hãy làm như sau." Dòng thứ hai kiểm tra xem thực thể chúng ta vừa thấy là "tiêu đề". Dòng tiếp theo lặp lại nội dung của thẻ. Bốn dòng thoát. Nếu nó không phải là thực thể tiêu đề thì vòng lặp lặp lại trên dòng thứ sáu. Chúng tôi chuyển hướng "xhtmlfile.xhtml" thành đầu vào tiêu chuẩn (cho read_domchức năng) và chuyển hướng đầu ra tiêu chuẩn sang "titleOfXHTMLPage.txt" (tiếng vang từ trước đó trong vòng lặp).

Bây giờ được đưa ra như sau (tương tự với những gì bạn nhận được từ việc liệt kê một nhóm trên S3) cho 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>

và vòng lặp sau:

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

Bạn sẽ nhận được:

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

Vì vậy, nếu chúng tôi đã viết một whilevòng lặp như của Yuzem:

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

Chúng tôi sẽ nhận được một danh sách tất cả các tệp trong nhóm S3.

EDIT Nếu vì một lý do local IFS=\>nào đó không phù hợp với bạn và bạn đặt nó trên toàn cầu, bạn nên đặt lại nó ở cuối chức năng như:

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

Nếu không, bất kỳ dòng chia tách nào bạn thực hiện sau đó trong tập lệnh sẽ bị rối tung.

EDIT 2 Để tách các cặp tên / giá trị thuộc tính, bạn có thể gia tăng read_dom()tương tự như vậy:

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

Sau đó viết hàm của bạn để phân tích cú pháp và lấy dữ liệu bạn muốn như thế này:

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
}

Sau đó, trong khi bạn read_domgọi parse_dom:

while read_dom; do
    parse_dom
done

Sau đó đưa ra đánh dấu ví dụ sau:

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

Bạn sẽ nhận được đầu ra này:

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

EDIT 3 một người dùng khác cho biết họ gặp vấn đề với nó trong FreeBSD và đề nghị lưu trạng thái thoát khỏi đọc và trả lại vào cuối read_dom như:

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

Tôi không thấy lý do tại sao điều đó không làm việc


2
Nếu bạn đặt IFS (dấu tách trường đầu vào) toàn cục, bạn nên đặt lại về giá trị ban đầu, tôi đã chỉnh sửa câu trả lời để có. Nếu không, bất kỳ phân tách đầu vào nào khác mà bạn thực hiện sau này trong tập lệnh của bạn sẽ bị rối tung. Tôi nghi ngờ lý do cục bộ không phù hợp với bạn là vì hoặc bạn đang sử dụng bash ở chế độ tương thích (như shbang của bạn là #! / Bin / sh) hoặc đó là phiên bản bash cổ.
chad

30
Chỉ vì bạn có thể viết trình phân tích cú pháp của riêng bạn, không có nghĩa là bạn nên.
Stephen Niedzielski

1
@chad chắc chắn nó đã nói điều gì đó về quy trình / cách thực hiện của AWS mà tôi đang tìm kiếm câu trả lời cho "bash xml" để quên nội dung của nhóm S3!
Alastair

2
@Alastair xem github.com/chad3814/s3scripts để biết tập hợp các tập lệnh bash mà chúng ta sử dụng để thao tác với các đối tượng S3
chad

5
Việc gán IFS trong một biến cục bộ là mong manh và không cần thiết. Chỉ cần làm : IFS=\< read ..., sẽ chỉ đặt IFS cho cuộc gọi đọc. (Lưu ý rằng tôi không có cách nào chứng thực việc sử dụng readđể phân tích xml, và tôi tin rằng làm như vậy là đầy rủi ro và nên tránh.)
William Pursell 27/11/13

64

Bạn có thể làm điều đó rất dễ dàng chỉ bằng bash. Bạn chỉ phải thêm chức năng này:

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

Bây giờ bạn có thể sử dụng rdom như đọc nhưng cho các tài liệu html. Khi được gọi rdom sẽ gán phần tử cho biến E và nội dung cho var C.

Ví dụ: để làm những gì bạn muốn làm:

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

bạn có thể giải thích về điều này? Tôi cá rằng điều đó hoàn toàn rõ ràng với bạn .. và đây có thể là một câu trả lời tuyệt vời - nếu tôi có thể nói những gì bạn đang làm ở đó .. bạn có thể phá vỡ nó thêm một chút nữa, có thể tạo ra một số đầu ra mẫu không?
Alex Gray

1
Tín dụng cho bản gốc - chiếc áo lót này rất thanh lịch và tuyệt vời.
maverick

1
hack tuyệt vời, nhưng tôi đã phải sử dụng dấu ngoặc kép như echo "$ C" để ngăn chặn sự mở rộng vỏ và giải thích chính xác các dòng kết thúc (phụ thuộc vào điều kiện)
user311174

8
Phân tích cú pháp XML bằng grep và awk là không ổn . Nó có thể là một sự thỏa hiệp chấp nhận được nếu các XML đủ đơn giản và bạn không mất quá nhiều thời gian, nhưng nó không thể được gọi là một giải pháp tốt bao giờ.
peterh - Tái lập Monica

59

Các công cụ dòng lệnh có thể được gọi từ các kịch bản shell bao gồm:

  • 4xpath - trình bao bọc dòng lệnh xung quanh gói 4Suite của Python
  • XMLStarlet
  • xpath - trình bao bọc dòng lệnh xung quanh thư viện XPath của Perl
  • Xidel - Hoạt động với URL cũng như các tệp. Cũng hoạt động với JSON

Tôi cũng sử dụng xmllint và xsltproc với các tập lệnh biến đổi XSL nhỏ để xử lý XML từ dòng lệnh hoặc trong các tập lệnh shell.


2
Tôi có thể tải xuống 'xpath' hoặc '4xpath' từ đâu?
Mở

3
có, một phiếu bầu / yêu cầu thứ hai - nơi để tải xuống các công cụ đó, hoặc bạn có nghĩa là người ta phải tự viết một trình bao bọc? Tôi thà không lãng phí thời gian để làm điều đó trừ khi cần thiết.
David

4
sudo apt-get cài đặt libxml-xpath-perl
Andrew Wagner

22

Bạn có thể sử dụng tiện ích xpath. Nó được cài đặt với gói Perl XML-XPath.

Sử dụng:

/usr/bin/xpath [filename] query

hoặc XMLStarlet . Để cài đặt nó trên openuse sử dụng:

sudo zypper install xmlstarlet

hoặc thử cnf xmltrên các nền tảng khác.


5
Sử dụng xml starlet chắc chắn là một lựa chọn tốt hơn so với việc viết serializer của chính mình (như được đề xuất trong các câu trả lời khác).
Bruno von Paris

Trên nhiều hệ thống, hệ thống xpathđược cài đặt sẵn không phù hợp để sử dụng làm thành phần trong tập lệnh. Xem ví dụ stackoverflow.com/questions/15461737/ cấp để biết thêm chi tiết.
tripleee

2
Trên Ubuntu / Debianapt-get install xmlstarlet
rubo77



5

bắt đầu từ câu trả lời của chad, đây là giải pháp làm việc HOÀN TOÀN để phân tích UML, với xử lý bình luận propper, chỉ với 2 chức năng nhỏ (hơn 2 bu bạn có thể trộn tất cả chúng). Tôi không nói rằng một trong số đó không hoạt động, nhưng nó có quá nhiều vấn đề với các tệp XML được định dạng kém: Vì vậy, bạn phải khó khăn hơn một chút để xử lý các bình luận và đặt sai chỗ / CR / TAB / v.v.

Mục đích của câu trả lời này là cung cấp sẵn sàng 2 chức năng bash cho bất kỳ ai cần phân tích cú pháp UML mà không cần các công cụ phức tạp sử dụng perl, python hoặc bất cứ thứ gì khác. Đối với tôi, tôi không thể cài đặt cpan, cũng như các mô-đun perl cho HĐH sản xuất cũ mà tôi đang làm việc và python không khả dụng.

Đầu tiên, một định nghĩa về các từ UML được sử dụng trong bài viết này:

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

EDIT: các chức năng được cập nhật, có xử lý:

  • Websphere xml (thuộc tính xmi và xmlns)
  • phải có một thiết bị đầu cuối tương thích với 256 màu
  • 24 sắc thái của màu xám
  • khả năng tương thích được thêm cho IBM AIX bash 3.2.16 (1)

Các hàm, đầu tiên là xml_read_dom, được gọi là đệ quy bởi 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
}

và cái thứ hai:

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
}

và cuối cùng, các hàm rtrim, trim và 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; }

Tô màu:

oh và bạn sẽ cần một số biến động màu sắc gọn gàng được xác định lúc đầu và cũng được xuất:

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

Làm thế nào để tải tất cả những thứ đó:

Hoặc bạn biết cách tạo các hàm và tải chúng thông qua FPATH (ksh) hoặc mô phỏng của FPATH (bash)

Nếu không, chỉ cần sao chép / dán mọi thứ trên dòng lệnh.

Làm thế nào nó hoạt động:

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

Với các bình luận Chế độ gỡ lỗi (-d) và các thuộc tính được phân tích cú pháp được in ra stderr


Tôi đang cố gắng sử dụng hai chức năng trên tạo ra các chức năng sau : ./read_xml.sh: line 22: (-1): substring expression < 0?
khmarbaise

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

xin lỗi khmarbaise, đây là các hàm bash shell. Nếu bạn muốn điều chỉnh chúng dưới dạng shell script, bạn chắc chắn phải mong đợi một số điều chỉnh nhỏ! Ngoài ra các chức năng được cập nhật xử lý các lỗi của bạn;)
scavenger

4

Tôi không biết về bất kỳ công cụ phân tích cú pháp XML shell thuần túy nào. Vì vậy, rất có thể bạn sẽ cần một công cụ được viết bằng một ngôn ngữ khác.

Mô-đun XML :: Twig Perl của tôi đi kèm với một công cụ như vậy : xml_grep, nơi bạn có thể sẽ viết những gì bạn muốn xml_grep -t '/html/head/title' xhtmlfile.xhtml > titleOfXHTMLPage.txt( -ttùy chọn cung cấp cho bạn kết quả dưới dạng văn bản thay vì xml)


4

Một công cụ dòng lệnh khác là Xidel mới của tôi . Nó cũng hỗ trợ XPath 2 và XQuery, trái với xpath / xmlstarlet đã được đề cập.

Tiêu đề có thể được đọc như sau:

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

Và nó cũng có một tính năng thú vị để xuất nhiều biến sang bash. Ví dụ

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

thiết lập $titletiêu đề và $imgcountsố lượng hình ảnh trong tệp, nên linh hoạt như phân tích cú pháp trực tiếp trong bash.


Đây chính xác là những gì tôi cần! :)
Thomas Daugaard

2

Vâng, bạn có thể sử dụng tiện ích xpath. Tôi đoán XML của Perl có chứa nó.


2

Sau một số nghiên cứu để dịch giữa các định dạng Linux và Windows của các đường dẫn tệp trong tệp XML, tôi đã tìm thấy các hướng dẫn và giải pháp thú vị về:


2

Mặc dù có khá nhiều tiện ích bảng điều khiển được làm sẵn có thể làm những gì bạn muốn, nhưng có lẽ sẽ mất ít thời gian hơn để viết một vài dòng mã bằng ngôn ngữ lập trình đa năng như Python mà bạn có thể dễ dàng mở rộng và thích nghi với bạn cần.

Đây là một kịch bản python sử dụng lxml để phân tích cú pháp - nó lấy tên của tệp hoặc URL làm tham số đầu tiên, biểu thức XPath làm tham số thứ hai và in các chuỗi / nút khớp với biểu thức đã cho.

ví dụ 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))

lxmlcó thể được cài đặt với pip install lxml. Trên Ubuntu bạn có thể sử dụng sudo apt install python-lxml.

Sử dụng

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

lxml cũng chấp nhận một URL làm đầu vào:

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

Lưu ý : Nếu XML của bạn có không gian tên mặc định không có tiền tố (ví dụ xmlns=http://abc...) thì bạn phải sử dụng ptiền tố (được cung cấp bởi 'hack') trong các biểu thức của bạn, ví dụ: //p:moduleđể lấy các mô-đun từ một pom.xmltệp. Trong trường hợp ptiền tố đã được ánh xạ trong XML của bạn, thì bạn sẽ cần sửa đổi tập lệnh để sử dụng tiền tố khác.


Ví dụ 2

Một tập lệnh một lần phục vụ mục đích hẹp là trích xuất tên mô-đun từ tệp maven apache. Lưu ý cách tên nút ( module) được tiền tố với không gian tên mặc định {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)

Điều này thật tuyệt vời khi bạn muốn tránh cài đặt các gói bổ sung hoặc không có quyền truy cập. Trên một máy xây dựng, tôi có thể biện minh cho thêm pip installhơn apt-gethoặc yumcuộc gọi. Cảm ơn!
E. Moffat

0

Phương pháp của Yuzem có thể được cải thiện bằng cách đảo ngược thứ tự <>các dấu hiệu trong rdomhàm và các phép gán biến, sao cho:

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

trở thành:

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

Nếu việc phân tích cú pháp không được thực hiện như thế này, thẻ cuối cùng trong tệp XML sẽ không bao giờ đạt được. Điều này có thể có vấn đề nếu bạn định xuất một tệp XML khác ở cuối whilevòng lặp.


0

Điều này hoạt động nếu bạn muốn các thuộc tính 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

Mặc dù có vẻ như "không bao giờ phân tích cú pháp XML, JSON ... từ bash mà không có công cụ thích hợp" là lời khuyên âm thanh, tôi không đồng ý. Nếu đây là công việc phụ, thật tuyệt vời khi tìm kiếm công cụ thích hợp, sau đó học nó ... Awk có thể làm điều đó trong vài phút. Các chương trình của tôi phải làm việc trên tất cả các loại dữ liệu được đề cập ở trên và nhiều loại dữ liệu hơn. Chết tiệt, tôi không muốn thử nghiệm 30 công cụ để phân tích 5-7-10 định dạng khác nhau mà tôi cần nếu tôi có thể đánh thức vấn đề trong vài phút. Tôi không quan tâm đến XML, JSON hay bất cứ điều gì! Tôi cần một giải pháp duy nhất cho tất cả chúng.

Ví dụ: chương trình SmartHome của tôi điều hành nhà của chúng tôi. Trong khi thực hiện, nó đọc rất nhiều dữ liệu ở quá nhiều định dạng khác nhau mà tôi không thể kiểm soát. Tôi không bao giờ sử dụng các công cụ phù hợp, chuyên dụng vì tôi không muốn dành nhiều phút hơn để đọc dữ liệu tôi cần. Với các điều chỉnh FS và RS, giải pháp awk này hoạt động hoàn hảo cho mọi định dạng văn bản. Nhưng, nó có thể không phải là câu trả lời thích hợp khi nhiệm vụ chính của bạn là làm việc chủ yếu với vô số dữ liệu ở định dạng đó!

Vấn đề phân tích cú pháp XML từ bash tôi gặp phải ngày hôm qua. Đây là cách tôi làm điều đó cho bất kỳ định dạng dữ liệu phân cấp. Như một phần thưởng - Tôi gán dữ liệu trực tiếp cho các biến trong tập lệnh bash.

Để làm cho nó dễ đọc hơn, tôi sẽ trình bày giải pháp theo từng giai đoạn. Từ dữ liệu thử nghiệm OP, tôi đã tạo một tệp: test.xml

Phân tích cú pháp XML nói bash và trích xuất dữ liệu trong 90 ký tự:

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

Tôi thường sử dụng phiên bản dễ đọc hơn vì nó dễ sửa đổi hơn trong cuộc sống thực vì tôi thường cần phải kiểm tra khác nhau:

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

Tôi không quan tâm định dạng được gọi như thế nào. Tôi chỉ tìm kiếm giải pháp đơn giản nhất. Trong trường hợp cụ thể này, tôi có thể thấy từ dữ liệu rằng dòng mới là dấu tách bản ghi (RS) và <> trường phân định (FS). Trong trường hợp ban đầu của tôi, tôi đã lập chỉ mục phức tạp 6 giá trị trong hai bản ghi, liên quan đến chúng, tìm khi dữ liệu tồn tại cộng với các trường (bản ghi) có thể tồn tại hoặc không tồn tại. Phải mất 4 dòng awk để giải quyết vấn đề một cách hoàn hảo. Vì vậy, hãy thích nghi với từng nhu cầu trước khi sử dụng nó!

Phần thứ hai chỉ đơn giản là nó có chuỗi mong muốn trong một dòng (RS) và nếu vậy, in ra các trường cần thiết (FS). Ở trên tôi mất khoảng 30 giây để sao chép và điều chỉnh từ lệnh cuối cùng mà tôi đã sử dụng theo cách này (dài hơn 4 lần). Và đó là nó! Thực hiện trong 90 ký tự.

Nhưng, tôi luôn cần lấy dữ liệu gọn gàng thành các biến trong tập lệnh của mình. Trước tiên tôi kiểm tra các cấu trúc như vậy:

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

Trong một số trường hợp tôi sử dụng printf thay vì in. Khi tôi thấy mọi thứ có vẻ tốt, tôi chỉ cần hoàn thành việc gán giá trị cho các biến. Tôi biết nhiều người nghĩ "eval" là "ác", không cần bình luận :) Thủ thuật hoạt động hoàn hảo trên cả bốn mạng của tôi trong nhiều năm. Nhưng hãy tiếp tục học nếu bạn không hiểu tại sao điều này có thể là thực hành tồi! Bao gồm các phép gán biến bash và khoảng cách rộng rãi, giải pháp của tôi cần 120 ký tự để làm mọi thứ.

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"
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.