脚本语言:Bash
处理目标:包含多行IP地址的原始文件
处理效果:
- 每个网络段分别输出为一行,顺序是IP的A,B,C段分别升序
- 每行输出中不连续IP用逗号分隔,连续D段用连字符连接首尾数字
需求场景说明
假设一份Excel表格中,有大量(千/万行)的包含IP地址的记录,IP地址列中的数据每个单元格都是一个单独的IP地址。
现在要求:
- 根据设备名称分组,并输出该分组包含的IP段
- 每组输出中按照IP地址的A.B.C.D段,每段的数字升序排列,有几个网段就输出几行
- 把每行的IP地址D段进行合并,连续的D段使用连字符(-)连接首位数字,不连续的直接用逗号分隔
预期合并效果
原始文件内容示例:
数据中心1区日志采集服务器1-3 10.0.0.3
数据中心1区日志采集服务器1-10 10.0.0.10
数据中心1区日志采集服务器1-11 10.0.0.11
数据中心1区日志采集服务器1-12 10.0.0.12
数据中心1区日志采集服务器1-19 10.0.0.19
数据中心2区日志采集服务器2-20 10.0.1.20
数据中心2区日志采集服务器2-21 10.0.1.21
数据中心2区日志采集服务器2-22 10.0.1.22
数据中心2区日志采集服务器2-45 10.0.1.45
数据中心2区日志采集服务器2-99 10.0.1.99
脚本处理后的效果
数据中心1区日志采集服务器
==> 10.0.0.3, 10.0.0.10-12, 10.0.0.19
数据中心2区日志采集服务器
==> 10.0.1.20-22, 10.0.1.45, 10.0.1.99
脚本思路梳理
- 主要的难点和整个需求的核心就是D段数字的处理,需要设计一个合理的格式化输出的流程控制,且封装在函数(本例:FormatD)中方便调用。
- 根据设备名称分组输出,这个需求比较简单,也不是本篇文章的重点,这里简单的使用
sed
的正则表达式模式匹配做字符串替换,切分出可以作为分组依据的前缀字符串,并用来从原始文件中grep
出需要的记录行。 - 根据IP地址网络段,把每个网络段的地址分行输出,需要使用“循环嵌套”的方式来保证顺序,这里也比较简单,因为
sort -n
命令可以把每段的数字做升序排序,只需要直接遍历出来就是符号顺序要求的。 - 由于IP地址的A.B.C.D四个段都需要分别处理,且需要使用有序的数据结构装载,所以考虑使用数组分别装载每个段的数字。
D段格式化处理的函数 - 核心流程控制
先贴代码,后做分析:
function FormatD {
d=($1)
net=$2
pre=${d[0]}
stat=0
echo -n "==> $net${d[0]}"
for ((i=1; i<${#d[@]}; i++)); do
# 上一次的迭代结果:连续
if [ $stat -eq 1 ]; then
# 本次迭代结果:不连续
# 输出:-${d[$((i-1))]}, ${d[$i]}
if [ ${d[$i]} -ne $((pre + 1)) ]; then
echo -n "-${d[$((i - 1))]}, $net${d[$i]}"
stat=0
pre=${d[$i]} # 更新pre变量
continue
# 特殊处理尾部连续
elif [ $i -eq $((${#d[@]} - 1)) ]; then
echo -n "-${d[$i]}"
# 本次迭代结果:连续
# 什么都不输出
else
stat=1
pre=${d[$i]} # 更新pre变量
continue
fi
elif [ $stat -eq 0 ]; then
# 上一次的迭代结果:不连续
# 本次迭代结果:不连续
# 输出:,${d[$i]}
if [ ${d[$i]} -ne $((pre + 1)) ]; then
echo -n ", $net${d[$i]}"
stat=0
pre=${d[$i]} # 更新pre变量
continue
# 特殊处理尾部连续
elif [ $i -eq $((${#d[@]} - 1)) ]; then
echo -n "-${d[$i]}"
# 本次迭代结果:连续
# 什么都不输出
else
stat=1
pre=${d[$i]} # 更新pre变量
continue
fi
fi
done
- 首先数组变量d接收位置传参
$1
数组中的所有元素 net
接收的位置传参$2
字符串内容就是IP地址的网络位(A.B.C)
pre
作为一个临时变量
,每一次循环结束前都重新赋值成本次访问的数组元素stat
变量作为代表状态
的标识变量
,使用了0和1两个数字布尔
值,代表上一次的迭代结果是连续还是不连续,0代表不连续,1代表连续。因为本次迭代到底输出什么东西,是要根据上次迭代来判断的,这也是整个流程控制的重点。
把IP地址的各段塞入数组中
先贴代码,后做分析:
function SliceIP2Arr {
netaddr=()
# 组合网络地址(A.B.C.*)
a=`cat ip.tmp | awk -F. '{print $1}'| sort -n | uniq`
a_arr=($a)
for i in ${a_arr[@]};do
b=`cat ip.tmp | grep "^$i." |awk -F. '{print $2}' | sort -n | uniq`
b_arr=($b)
for j in ${b_arr[@]}; do
c=`cat ip.tmp | grep "^$i.$j." | awk -F. '{print $3}' | sort -n | uniq`
c_arr=($c)
for k in ${c_arr[@]}; do
#echo $i.$j.$k
net=`echo $i.$j.$k.`
netaddr+=($net)
done
done
done
#echo ${#netaddr[@]}
}
netaddr
赋值为空数组,这个操作主要就是为了清空它,如果不清空,内存中的netaddr数组会追加进每次循环的结果,这并不是我们想要的效果,我们需要每个设备名分组使用一个全新的数组来装载这个分组的网段- 循环嵌套就很简单了,从左到右循环,保证树形结构和顺序,不会得到不存在的网段
读取文件
先贴代码,后做分析:
function Main {
file=text.txt
cat $file | awk '{print $1}' | sed -E 's/[0-9]+-[0-9]+$//' | sed -E 's/[0-9]+$//' | sort -n | uniq | while read preword; do
echo $preword
# 根据原始文件的列数选择 $FIELD 写入临时文件
cat $file | grep "^$preword" | awk '{print $NF}' > ip.tmp
SliceIP2Arr
# 匹配网络地址中的主机地址(*.*.*.D)
for net in ${netaddr[@]}; do
d=`cat ip.tmp | grep "^$net"| awk -F. '{print $4}'| sort -n | uniq`
d_arr=($d)
FormatD "${d_arr[*]}" $net
done
done
}
Main
- 注意把
text.txt
替换成需要处理的原始文件名 - 保证原始文件不串列,且每个字段最好不为空,IP地址放在最后一列
- 脚本使用了一个文件
ip.tmp
作为每次循环中暂时存储IP地址的临时文件,可以在脚本的开头结尾添加删除命令,保证运行目录的整洁。
可以下载附件中的bash脚本自己测试一下,或者把文中的三个代码段按顺序拼接在一起写入脚本中测试。