![在 Bash 中从特定/一般 XML 文件生成树输出](https://linux22.com/image/1002555/%E5%9C%A8%20Bash%20%E4%B8%AD%E4%BB%8E%E7%89%B9%E5%AE%9A%2F%E4%B8%80%E8%88%AC%20XML%20%E6%96%87%E4%BB%B6%E7%94%9F%E6%88%90%E6%A0%91%E8%BE%93%E5%87%BA.png)
我正在尝试在 Bash 中从 XML 文件生成一棵树。
这是 XML 文件的一部分:
<menu name="main_menu" display="Main Menu">
<application name="load_profiles" display="Load Profile"/>
<application name="save_profiles" display="Save Profile"/>
<application name="remove_profiles" display="Delete Profile"/>
</menu>
我曾尝试使用 CAT、GREP 和 AWK:
cat menu.xml | grep menu\ name | awk -v FS="(display=\"|\" help)" '{print $2}' > menulist.txt
我首先使用包含“菜单名称”的行进行 GREP 搜索,然后打印“display="'和'”help' 之间的测试,得到以下输出:
Main Menu">
Broadband
Load and Save Profiles
xDSL Interface
但我想要的是 Grep 所有包含“菜单名称”、“参数类型”、“应用程序名称”和“值 ID”的行,并在树状输出中打印它们的显示名称。我不确定如何从多行 Grep 多个值并从中打印特定字符串。
然后我发现使用 XML 解析器工具来执行此操作相对容易。所以我尝试使用 XMLStarlet:
xmlstarlet el menu.xml | awk -F'/' 'BEGIN{print "digraph{"}{print $(NF-1)" -> "$NF}END{print"}"}' > menumenutxt.txt
使用此命令我发现以下输出:
menu -> menu
menu -> onenter
menu -> menu
menu -> application
menu -> application
menu -> application
menu -> parameter
parameter -> value
parameter -> value
这看起来确实更好,更接近我想要的。但它没有打印显示名称。
我想要打印的内容如下:
Main Menu ->
-> Broadband
-> Load and Save Profiles
-> Load Profile
-> Save Profile
-> Delete Profile
或者以下内容:
Main Menu
-> Broadband
--> Load and Save Profiles
---> Load Profile
---> Save Profile
---> Delete Profile
我的目标是获得尽可能接近它的输出。有人能建议我该怎么做吗?
答案1
改编自xmlstarlet 文档:
xmlstarlet sel -T -t -m '//*' \
-i '@display' \
-m 'ancestor-or-self::*' \
-i '(position()=last())' \
-o '-> ' -v '@display' -b \
-o $'\t' -b \
-n foo.xml
例如:
使用 xml sel 打印 XML 元素的结构(高级 XPath 表达式和 xml sel 命令用法)
xml sel -T -t -m '//*' \ -m 'ancestor-or-self::*' -v 'name()' -i 'not(position()=last())' -o . -b -b -n \ xml/structure.xml
结果输出:
a1 a1.a11 a1.a11.a111 a1.a11.a111.a1111 a1.a11.a112 a1.a11.a112.a1121 a1.a12 a1.a13 a1.a13.a131
从这里开始,我们需要修改的内容是:
- 打印
display
属性而不是name
,因此@display
而不是name()
- 只打印最后一个元素。我们已经测试过打印
.
除最后一个元素之外的所有元素,因此很容易将其反转。 - 打印制表符以缩进(我们可以在每个元素之后执行此操作,它只会留下尾随的、不可见的制表符),因此在 bash 中只需输入
-o $'\t'
.$'\t'
就会得到一个制表符。 - 仅打印具有该
display
属性的元素,因此-i '@display'
我缩进了上面的命令以使流程更清晰。
我得到的输出:
$ xmlstarlet sel -T -t -m '//*' -i '@display' -m 'ancestor-or-self::*' -i '(position()=last())' -o '-> ' -v '@display' -b -o $'\t' -b -n foo.xml
-> English
-> Main Menu
-> Broadband
-> Load and Save Profiles
-> Load Profile
-> Save Profile
-> Delete Profile
-> Interface
-> xDSL
-> SFP
-> Ethernet
-> SHDSL
-> xDSL Interface
-> xDSL Mode
-> Annex A/M
-> Annex B/J
-> MAC Address
-> MAC Address
-> Vectoring Mode
-> Disabled
-> Enabled
-> Friendly
-> G.FAST
-> Disabled
-> Enabled
经过一番思考,以下更简单:
xmlstarlet sel -T -t -m '//*' \
-i '@display' \
-m 'ancestor::*' \
-o $'\t' -b \
-o '-> ' -v '@display' -n foo.xml
使用ancestor::*
而不是可以ancestor-or-self::*
更容易地正确打印标签,并消除对最后一个元素的额外测试。
类似的输出,但没有尾随制表符:
-> English
-> Main Menu
-> Broadband
-> Load and Save Profiles
-> Load Profile
-> Save Profile
-> Delete Profile
-> Interface
-> xDSL
-> SFP
-> Ethernet
-> SHDSL
-> xDSL Interface
-> xDSL Mode
-> Annex A/M
-> Annex B/J
-> MAC Address
-> MAC Address
-> Vectoring Mode
-> Disabled
-> Enabled
-> Friendly
-> G.FAST
-> Disabled
-> Enabled
答案2
(如果尚未安装,请安装 xidel)
xidel ex.xml \
-e '//@display/concat(substring("------",1,count(ancestor::*)),">",.)'
substring("------",1,n)
是用 n 个“-”构建字符串的一种肮脏的方法