案例十五:格式化输出xml文件
在工作中我们多多少少都接触过XML文件,它的格式非常有规律性,但读起来因为有太多的标签(<>),不能一目了然,就比如下面的一段配置:
<configuration>
<artifactItems>
<artifactItem>
<groupId>zzz</groupId>
<artifactId>aaa</artifactId>
</artifactItem>
<artifactItem>
<groupId>xxx</groupId>
<artifactId>yyy</artifactId>
</artifactItem>
</artifactItems>
</configuration>
本案例的需求是从上面的XML文本中提取groupId和artifactId,并输出为如下格式:
artifactItem:groupId:zzz
artifactItem:artifactId:aaa
artifactItem:groupId:xxx
artifactItem:artifactId:yyy
知识点一:关于XML的小常识
XML(Extensible Markup Language)翻译成中文叫做:可扩展标记语言。如果你接触过HTML,那么你会觉得XML跟HTML挺像,其实它们都是一种标记语言。XML主要用来传送以及携带数据信息,而不是用来展示的,所以我们读起来就有点障碍。
有不少服务的配置文件就是一个XML文本,在XML文本中定义对应的配置,就比如本案例中的示例文本就是一个配置文件。XML的作用主要在于存储数据,它以纯文本格式进行存储,因此提供了一种独立于软件和硬件的数据存储方法。这让创建不同应用程序可以共享的数据变得更加容易。由于XML文本的格式是固定的,无论是Windows、Linux或者Mac等其他操作系统,都可以识别,所以它的兼容性很好。
有一点,我们必须要知道,那就是XML是一种不作为的标记语言,即,它不像HTML需要被解析、执行然后展示漂亮的网页,它存在的意义仅仅是结构化、存储以及传输信息。
知识点二:截取文档中两个关键词中间的行
需求是,把文本中包含'abc'和包含'123'中间的部分打印出来,假设'abc'在'123'上面。如果使用sed,一条命令即可实现:
sed -n '/abc/,/123/p' 1.txt
不过这样依然有abc和123的行,要想去掉它们也很简单,如下:
sed -n '/abc/,/123/p' 1.txt|sed '/abc/d;/123/d'
如果文本中有多个abc和123,则会同时把所有符合条件的行全部打印出来,下面我再提供一个比较笨的土方法,帮助大家练习逻辑思维能力。
#!/bin/bash
#先获取abc和123所在行的行号
egrep -n 'abc|123' 1.txt |awk -F ':' '{print $1}' > /tmp/line_number.txt
#计算一共有多少包含abc和123的行
n=`wc -l /tmp/line_number.txt|awk '{print $1}'`
#计算一共有多少对abc和123
n2=$[$n/2]
for i in `seq 1 $n2`
do
#每次循环都要处理两行,第一次是1,2,第二次是3,4,依此类推
m1=$[$i*2-1]
m2=$[$i*2]
#每次遍历都要获取abc和123的行号
nu1=`sed -n "$m1"p /tmp/line_number.txt`
nu2=`sed -n "$m2"p /tmp/line_number.txt`
#获取abc下面一行的行号
nu3=$[$nu1+1]
#获取123上面一行的行号
nu4=$[$nu2-1]
#用sed把abc和123中间的行打印出来
sed -n "$nu3,$nu4"p 1.txt
#便于分辨,添加分隔行符号
echo "============="
done
提供一个测试的文本1.txt,内容如下
alskdfkjlasldkjfabalskdjflkajsd
asldkfjjk232k3jlk2
alskk2lklkkabclaksdj
skjjfk23kjalf09wlkjlah lkaswlekjl9
aksjdf
123asd232323
aaaaaaaaaa
222222222222222222
abcabc12121212
fa2klj
slkj32k3j
22233232123
bbbbbbb
ddddddddddd
用sed处理,结果是这样的:
# sed -n '/abc/,/123/p' 1.txt|sed '/abc/d;/123/d'
skjjfk23kjalf09wlkjlah lkaswlekjl9
aksjdf
fa2klj
slkj32k3j
将上面的脚本保存到mysed.sh里,执行后结果是这样的:
# sh mysed.sh
skjjfk23kjalf09wlkjlah lkaswlekjl9
aksjdf
=============
fa2klj
slkj32k3j
=============
案例分析
1)首先需要找到<artifactItem>和</artifactItem>中间的数据段,针对这部分数据进行分析
2)可以找到XML文档中包含<artifactItem>和</artifactItem>的行的行号,然后使用sed把这部分内容截取出来
3)处理截取出来的数据段,使用sed、awk截取关键词以及对应的值
本案例参考脚本
#!/bin/bash
#按要求输出XML内容,本脚本定制性较强,不可通用
#作者:阿铭
#日期:2018-10-26
#假设要处理的XML文档名字为test.xml
#获取<artifactItem>和</artifactItem>所在的行号
grep -n 'artifactItem>' test.xml |awk '{print $1}' |sed 's/://' > /tmp/line_number.txt
#计算<artifactItem>和</artifactItem>的行一共有多少行
n=`wc -l /tmp/line_number.txt|awk '{print $1}'`
#定义获取关键词和其值的函数
get_value(){
#$1和$2为函数的两个参数,即<artifactItem>下一行和</artifactItem>上一行的行号(这个操作在下面)
#截取出<artifactItem>和</artifactItem>中间的内容,然后获取关键词(如groupId)和其对应的值,写入/tmp/value.txt
sed -n "$1,$2"p test.xml|awk -F '<' '{print $2}'|awk -F '>' '{print $1,$2}' > /tmp/value.txt
#遍历整个/tmp/value.txt文档
cat /tmp/value.txt|while read line
do
#x为关键词,如groupId
#y为关键词的值
x=`echo $line|awk '{print $1}'`
y=`echo $line|awk '{print $2}'`
echo artifactItem:$x:$y
done
}
#由于/tmp/line_number.txt是成对出现的,n2为一共多少对
n2=$[$n/2]
#针对每一对,打印关键词和对应的值
for j in `seq 1 $n2`
do
#每次循环都要处理两行,第一次是1,2,第二次是3,4,依此类推
m1=$[$j*2-1]
m2=$[$j*2]
#每次遍历都要获取<artifactItem>和</artifactItem>的行号
nu1=`sed -n "$m1"p /tmp/line_number.txt`
nu2=`sed -n "$m2"p /tmp/line_number.txt`
#获取<artifactItem>下面一行的行号
nu3=$[$nu1+1]
#获取</artifactItem>上面一行的行号
nu4=$[$nu2-1]
get_value $nu3 $nu4
done
执行该脚本,结果和预期保持一致:
artifactItem:groupId:zzz
artifactItem:artifactId:aaa
artifactItem:groupId:xxx
artifactItem:artifactId:yyy