Bash에서 XML을 구문 분석하는 방법은 무엇입니까?


답변:


153

이것은 실제로 Yuzem의 대답에 대한 설명 일뿐 이지만 다른 사람에게 많은 편집을해야한다고 생각하지 않았으며 주석은 형식을 허용하지 않습니다.

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

"rdom"대신 "read_dom"이라고 부르고 약간 간격을두고 더 긴 변수를 사용하십시오.

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

이제 read_dom이라는 함수를 정의합니다. 첫 번째 행은 IFS (입력 필드 구분 기호)를이 함수의 로컬로 만들고>로 변경합니다. 즉, 공간, 탭 또는 줄 바꿈에서 자동으로 분할되는 대신 데이터를 읽을 때 '>'에서 분할됩니다. 다음 줄은 stdin에서 입력을 읽도록 지시하고 개행에서 멈추지 말고 '<'문자 (deliminate 플래그의 경우 -d)가 표시되면 중지하십시오. 읽은 내용은 IFS를 사용하여 분할되고 변수 ENTITY 및 CONTENT에 지정됩니다. 따라서 다음을 수행하십시오.

<tag>value</tag>

read_dom'<'가 첫 번째 문자이므로 빈 문자열 을 얻는 첫 번째 호출 입니다. '>'문자가 없기 때문에 IFS에 의해 ''로 분할됩니다. 그런 다음 읽기는 빈 문자열을 두 변수 모두에 할당합니다. 두 번째 호출은 문자열 'tag> value'를 가져옵니다. 그런 다음 IFS에 의해 두 필드 'tag'와 'value'로 나뉩니다. 읽기는 다음과 같은 변수를 할당합니다 ENTITY=tagCONTENT=value. 세 번째 호출은 문자열 '/ tag>'를 가져옵니다. 그것은 IFS에 의해 '/ tag'와 ''의 두 필드로 나뉩니다. 읽기는 다음과 같은 변수를 할당합니다 ENTITY=/tagCONTENT=. 파일 끝에 도달했기 때문에 네 번째 호출은 0이 아닌 상태를 반환합니다.

이제 그의 while 루프는 위와 일치하도록 약간 정리했습니다.

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

첫 번째 줄은 "read_dom 함수가 0 상태를 반환하는 동안 다음을 수행합니다."라고 말합니다. 두 번째 줄은 방금 본 엔터티가 "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 => 

따라서 whileYuzem과 같은 루프를 작성하면

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

EDIT 3 다른 사용자 는 FreeBSD에서 문제가 있다고 말했으며 읽기에서 종료 상태를 저장하고 read_dom의 끝에서 그것을 반환하도록 제안했습니다.

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

왜 작동하지 않는지 알 수 없습니다.


2
IFS (입력 필드 구분 기호)를 전역으로 만들면 마지막에 원래 값으로 다시 설정해야합니다. 답을 편집했습니다. 그렇지 않으면 나중에 스크립트에서 수행 한 다른 입력 분할이 엉망이됩니다. 로컬이 작동하지 않는 이유는 호환성 모드에서 bash를 사용하고 있거나 (shbang이 #! / bin / sh와 같은) bash의 고대 버전이기 때문입니다.
chad

30
자신 만의 파서를 작성할 수 있다고해서 꼭 그렇게해야하는 것은 아닙니다.
Stephen Niedzielski

1
@chad 확실히 나는 또한에 "bash는 XML"에 대한 답변을 검색 할 것을 AWS '워크 플로우 / 구현에 대한 뭔가를 말한다 는 wget S3 버킷의 내용을!
Alastair

2
@Alastair 볼 github.com/chad3814/s3scripts을 우리가 S3 객체를 조작하는 데 사용하는 bash는 스크립트의 세트
차드

5
로컬 변수에 IFS를 할당하는 것은 취약하며 필요하지 않습니다. 그냥 수행 IFS=\< read ...만 읽기 호출에 대한 IFS를 설정할 것이다. (나는 readXML을 파싱하기 위해 사용하는 관습을 결코지지하지 않으며 , 그렇게하는 것은 위험에 처해있어서 피해야한다고 믿는다.)
William Pursell

64

bash 만 사용하면 매우 쉽게 할 수 있습니다. 이 기능 만 추가하면됩니다 :

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

이제 rdom을 읽기와 비슷하지만 HTML 문서에 사용할 수 있습니다. rdom을 호출하면 요소를 변수 E에, 내용을 var C에 할당합니다.

예를 들어 원하는 작업을 수행하려면

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

이것에 대해 자세히 설명해 주시겠습니까? 나는 그것이 당신에게 완벽하게 분명하다는 것을 내기했습니다.. 그리고 이것은 훌륭한 답변이 될 수 있습니다-내가 당신이하고있는 일을 말할 수 있다면 .. 좀 더 분해하여 샘플 출력을 생성 할 수 있습니까?
Alex Gray

1
원본에 대한 신념-이 1 라이너는 너무나 우아하고 훌륭합니다.
maverick

1
훌륭한 해킹이지만 쉘 확장과 엔드 라인의 올바른 해석을 막기 위해 echo "$ C"와 같은 큰 따옴표를 사용해야했습니다 (
enconding

8
grep과 awk로 XML을 구문 분석하는 것은 좋지 않습니다 . XML이 충분히 단순하고 시간이 너무 많지 않으면 좋은 해결책이라고 할 수없는 경우에는 타협 할 수 있습니다.
peterh - 분석 재개 모니카

59

셸 스크립트에서 호출 할 수있는 명령 줄 도구는 다음과 같습니다.

  • 4xpath -Python의 4Suite 패키지를 둘러싼 명령 줄 래퍼
  • XMLStarlet
  • xpath-Perl의 XPath 라이브러리 주위의 명령 행 랩퍼
  • Xidel- 파일 및 URL과 함께 작동합니다. JSON 과도 작동

또한 XSL 변환 스크립트가 거의없는 xmllint 및 xsltproc을 사용하여 명령 줄 또는 셸 스크립트에서 XML 처리를 수행합니다.


2
'xpath'또는 '4xpath'는 어디에서 다운로드 할 수 있습니까?
Opher

3
예, 두 번째 투표 / 요청-해당 도구를 다운로드 할 위치 또는 수동으로 래퍼를 작성해야합니까? 필요하지 않으면 시간을 낭비하지 않겠습니다.
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
우분투 / 데비안에apt-get install xmlstarlet
rubo77



5

차드의 대답에서 시작하여, 여기에는 주석을 적절하게 처리하고 2 개의 작은 기능 (2 개 이상을 모두 혼합 할 수 있음)으로 UML을 구문 분석하는 완벽한 작업 솔루션이 있습니다. 나는 chad의 것이 전혀 작동하지 않았다고 말하지는 않지만 형식이 잘못된 XML 파일과 관련하여 너무 많은 문제가있었습니다.

이 답변의 목적은 perl, python 또는 다른 것을 사용하는 복잡한 도구없이 UML을 구문 분석 해야하는 모든 사람에게 즉시 사용 가능한 즉시 bash 함수를 제공하는 것입니다. 나에 관해서는 cpan을 설치할 수 없으며 이전에 사용중인 이전 운영 체제 용 펄 모듈을 설치할 수 없으며 파이썬을 사용할 수 없습니다.

먼저,이 포스트에서 사용 된 UML 단어의 정의 :

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

편집 : 업데이트 된 기능, 처리 :

  • Websphere xml (xmi 및 xmlns 속성)
  • 256 색의 호환되는 터미널이 있어야합니다
  • 회색 음영 24 개
  • IBM AIX bash 3.2.16 (1)에 대한 호환성이 추가되었습니다.

함수는 먼저 xml_read에 의해 재귀 적으로 호출되는 xml_read_dom입니다.

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 쉘 함수입니다. 쉘 스크립트로 적용하려면 약간의 조정이 필요합니다. 또한 업데이트 된 함수는 오류를 처리합니다;)
청소부

4

순수한 쉘 XML 파싱 도구를 모른다. 따라서 다른 언어로 작성된 도구가 필요할 것입니다.

내 XML :: Twig Perl 모듈에는 다음과 같은 도구가 포함되어 있습니다. xml_grep, 원하는 곳을 쓸 수 있습니다 xml_grep -t '/html/head/title' xhtmlfile.xhtml > titleOfXHTMLPage.txt( -t옵션은 결과를 XML 대신 텍스트로 제공합니다)


4

또 다른 명령 줄 도구는 새로운 Xidel 입니다. 또한 이미 언급 된 xpath / xmlstarlet과 달리 XPath 2 및 XQuery를 지원합니다.

제목은 다음과 같이 읽을 수 있습니다.

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

또한 여러 변수를 bash로 내보내는 멋진 기능도 있습니다. 예를 들어

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

파일 $title의 제목과 $imgcount이미지 수로 설정 합니다. 이는 bash에서 직접 파싱하는 것만 큼 유연해야합니다.


이것이 바로 내가 필요한 것입니다! :)
Thomas Daugaard

2

xpath 유틸리티를 사용할 수 있습니다. perl의 XML :: Xpath에 포함되어 있다고 생각합니다.


2

XML 파일에서 Linux와 Windows 형식의 파일 경로 변환에 대한 연구를 한 후 흥미로운 자습서와 솔루션을 찾았습니다.


2

원하는 기능을 수행 할 수있는 기성품 콘솔 유틸리티는 상당히 많지만, 쉽게 확장하고 적용 할 수있는 Python과 같은 범용 프로그래밍 언어로 몇 줄의 코드를 작성하는 데 시간이 덜 걸릴 것입니다 너의 요구.

다음은 lxml파싱에 사용하는 python 스크립트 입니다. 파일 또는 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. 우분투에서는을 사용할 수 있습니다 sudo apt install python-lxml.

용법

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

lxml URL을 입력으로 허용합니다.

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

참고 : XML에 접두사가없는 기본 네임 스페이스 (예 :)가 있으면 식에서 접두어 ( 'hack'으로 제공) xmlns=http://abc...를 사용해야합니다 p(예 : 파일 //p:module에서 모듈 가져 오기) pom.xml. 경우에 p접두사가 이미 XML에 매핑되는, 당신은 다른 접두사를 사용하는 스크립트를 수정해야합니다.


실시 예 2

아파치 메이븐 파일에서 모듈 이름을 추출하는 좁은 목적을 제공하는 일회용 스크립트입니다. 노드 이름 ( 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 파일의 마지막 태그에 도달하지 않습니다. while루프 끝에서 다른 XML 파일을 출력하려는 ​​경우 문제가 될 수 있습니다 .


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

-1

"적절한 도구없이 bash에서 XML, JSON을 구문 분석하지 마십시오"는 건전한 조언이지만, 나는 동의하지 않습니다. 이것이 부수적 인 일이라면, 적절한 도구를 찾아서 배우는 것은 허리가 가득합니다 ... Awk는 몇 분 안에 할 수 있습니다. 내 프로그램은 위에서 언급 한 모든 종류의 데이터에서 작동해야합니다. 지옥, 나는 몇 분 안에 문제를 해결할 수 있다면 5-7-10 개의 다른 형식을 구문 분석하는 30 가지 도구를 테스트하고 싶지 않습니다. XML, JSON 등은 신경 쓰지 않습니다! 나는 그들 모두를위한 단일 솔루션이 필요합니다.

예를 들어 : 나의 SmartHome 프로그램은 우리 집을 운영합니다. 그것을하는 동안 제어 할 수없는 너무 많은 다른 형식으로 많은 양의 데이터를 읽습니다. 필요한 데이터를 읽는 데 몇 분 이상 걸리지 않기 때문에 전용 도구를 사용하지 않습니다. FS 및 RS 조정을 통해이 awk 솔루션은 모든 텍스트 형식에 완벽하게 작동합니다. 그러나 기본 작업이 주로 해당 형식의 많은 데이터로 작업하는 경우에는 정답이 아닐 수도 있습니다!

어제 bash에서 XML을 파싱하는 문제. 다음은 모든 계층 적 데이터 형식에 대한 방법입니다. 보너스로-bash 스크립트의 변수에 직접 데이터를 할당합니다.

얇은 글씨를보다 쉽게 ​​읽을 수 있도록 단계별로 솔루션을 제시하겠습니다. OP 테스트 데이터에서 test.xml 파일을 작성했습니다.

bash에서 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 개의 값을 복잡한 색인화하여 관련 시켰습니다. 데이터가 존재하는 시점과 필드 (레코드)가 존재하거나 존재하지 않는 시간을 찾으십시오. 문제를 완벽하게 해결하려면 4 줄의 awk가 필요했습니다. 따라서 아이디어를 사용하기 전에 각각의 필요에 맞게 조정하십시오!

두 번째 부분은 단순히 줄에 원하는 문자열 (RS)이 있고 필요한 경우 필요한 필드 (FS)를 인쇄하는 것입니다. 위의 방법으로 내가 사용한 마지막 명령 (4 배 더 길다)을 복사하고 적용하는 데 약 30 초가 걸렸습니다. 그리고 그게 다야! 90 자로 완료했습니다.

그러나 항상 스크립트의 변수에 데이터를 깔끔하게 가져와야합니다. 먼저 구문을 다음과 같이 테스트합니다.

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

어떤 경우에는 인쇄 대신 printf를 사용합니다. 모든 것이 잘 보이면 변수에 값을 할당하는 것만으로 끝납니다. 나는 많은 사람들이 "eval"이 "evil"이라고 생각한다는 것을 알고 있습니다. 그러나 이것이 왜 나쁜 습관인지 이해할 수 없다면 계속 배우십시오! 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.