如何在Bash中解析XML?

18 浏览
0 Comments

如何在Bash中解析XML?

理想情况下,我希望能够做到的是:

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

admin 更改状态以发布 2023年5月21日
0
0 Comments

可以从Shell脚本调用的命令行工具包括:

  • 4xpath - Python的4Suite包的命令行包装器

  • XMLStarlet

  • xpath - Perl XPath库的命令行包装器

    sudo apt-get install libxml-xpath-perl
    

  • Xidel - 还可以处理JSON以及URL和文件

我也使用xmllint和xsltproc,配合小的XSL转换脚本,从命令行或shell脚本中进行XML处理。

0
0 Comments

这仅仅是对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。 因此,取以下内容:

value

第一次调用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”。下一行回显标签的内容。第四行退出。如果不是title实体,则循环重复在第六行。我们将“xhtmlfile.xhtml”重定向到标准输入(对于read_dom函数),并将标准输出重定向到“titleOfXHTMLPage.txt”(循环中的先前回显)。

现在假设有以下内容(类似于在S3上列出存储桶时得到的内容)用于input.xml


  sth-items
  false
  
    item-apple-iso@2x.png
    2011-07-25T22:23:04.000Z
    "0032a28286680abee71aed5d059c6a09"
    1785
    STANDARD
  

以及以下循环:

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 => "0032a28286680abee71aed5d059c6a09"
/ETag => 
Size => 1785
/Size => 
StorageClass => STANDARD
/StorageClass => 
/Contents => 

因此,如果我们像Yuzem那样编写了一个while循环:

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

然后,给出以下示例标记:


  bars content
  foos content

您应该得到以下输出:

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

编辑3另一个用户表示他们在FreeBSD中遇到了问题,并建议保存read的退出状态,并在read_dom的末尾返回它,如下所示:

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

我不认为这是有问题的。

0