AWK高级应用
在进行数据割接时,需要对其导出脚本的日志文件进行统计分析以便核对是否有数据没有导出的情况!该日志文件的格式都是固定的,可以使用脚本来完成统计分析,而且脚本很灵活小巧。
因为其复杂的语法和不明确的错误提示,造成awk的使用者进而远之,awk整体上比较难掌握。Awk是一种自解释的编程语言。而awk强大的文本处理功能正好能够胜任该工作。如果要格式化报文或从一个大的文本文件中抽取数据包,那么awk可以完成这些任务。它在文本浏览和数据的熟练使用上性能优异。
Awk的特性网上有很多资料,大家可以参考一下。本次所运用的awk的特性有:
Awk数组、printf修饰符、内置字符串函数、awk操作符、条件操作符。
要想使用好awk,必须对awk的语法有一定的认识,否则你会被一大堆莫名其妙的错误所包围。
Awk语句都是由模式和动作组成。模式的包括俩个关键字,BEGIN和END。BEGIN模式在awk遍历文本前调用,用来打印一些头信息或是声明一些全局变量。例如:
Log.dat记录着日志信息,日志为日期-ip-手机号码。
linux:/home/dss/dss/logs/msgs> cat log.dat
20091111-172230-665-10.168.38.63-15035198115
20091111-172230-738-10.168.38.65-13840784654
20091111-172238-571-10.168.38.63-15929933330
20091111-172238-668-10.168.38.63-13666463997
20091111-172240-262-10.168.38.68-13591931301
20091111-172242-24-10.168.38.63-15041615076
20091111-172248-427-10.168.38.63-13409199466
如果我想统计来自ip10.168.38.63的日志有几个,命令如下:
linux:/home/dss/dss/logs/msgs> awk -F'-' 'BEGIN {printf "%-15s %s\n","IP","LOG"printf “===============================================”}{if($4~/10.168.38.63/)printf "%-15s %s\n", $4,$0}END{}' log.dat
IP LOG
10.168.38.63 20091111-172230-665-10.168.38.63-15035198115
10.168.38.63 20091111-172238-571-10.168.38.63-15929933330
10.168.38.63 20091111-172238-668-10.168.38.63-13666463997
10.168.38.63 20091111-172242-24-10.168.38.63-15041615076
10.168.38.63 20091111-172248-427-10.168.38.63-13409199466
其中头信息IP和LOG在遍历log.dat前打印!
而END模式在遍历完文件最后执行,可以用于信息汇总,或打印结束日志。
实际动作需要包含在{}之间,如果不指明,默认动作是打印所有行。
Awk的域和记录,awk执行时,其浏览域标记为$1,$2… $n。这种方法称为域标识。使用这些域标识将更容易对域进行进一步处理。$0为匹配整行,看个例子:
linux:/home/dss/dss/logs/msgs> awk -F'-' '{print $4, $5}' log.dat
10.168.38.63 15035198115
10.168.38.65 13840784654
10.168.38.63 15929933330
10.168.38.63 13666463997
10.168.38.68 13591931301
10.168.38.63 15041615076
10.168.38.63 13409199466
以上命令只打印第4列和第5列。并且域分隔符为-,默认的域分隔符为空格,使用-F参数指定域分隔符。
awk有许多内置变量用来设置环境信息。这些变量可以被改变。下面列出一些最常使用的一些变量,并给出其基本含义。
ARGC 命令行参数个数
ARGV 命令行参数排列
ENVIRON 支持队列中系统环境变量的使用
FILENAME awk浏览的文件名
FNR 浏览文件的记录数
FS 设置输入域分隔符,等价于命令行- F选项
NF 浏览记录的域个数
NR 已读的记录数
OFS 输出域分隔符
ORS 输出记录分隔符
RS 控制记录分隔符
awk有许多强大的字符串函数,这些字符串函数在处理文本方面起着很重要的作用。通过这些内置字符串处理函数可以很随意的定义一些自己想要的函数。下面为awk中内置字符串处理函数。
gsub (r, s) 在整个$0中用s替代r
gsub (r, s, t) 在整个t中用s替代r
index (s , t) 返回s中字符串t的第一位置
length ( s ) 返回s长度
match (s , r) 测试s是否包含匹配r的字符串
split (s , a , fs) 在f s上将s分成序列a
sprint (fmt , exp) 返回经f m t格式化后的e x p
sub (r, s) 用$ 0中最左边最长的子串代替s
substr (s , p) 返回字符串s中从p开始的后缀部分
substr (s , p , n) 返回字符串s中从p开始长度为n的后缀部分
如下我只要使用index和substr函数就可以组合出很强大的自定义函数:
function substrmid(src, begin, end) {
startindex=index(src,begin) + length(begin)
temp=substr(src,startindex)
endindex=index(temp,end) - length(end)
return substr(src,startindex,endindex)
}
function substrbefore(src, end) {
endindex=index(src,end)-length(end)
return substr(src,0,endindex)
}
function substrafter(src, begin) {
startindex=index(src,begin) + length(begin)
return substr(src,startindex)
}
自定义函数substrbefore(src, end)是截取src中end之前的字符串,substrmid(src, begin, end)是截取src中介于begin和end之间的字符串(不包含begin和end),substrend(src, end)是截取src中end之后的字符换,例如:
Substrmid(“2009-11-01”, “-”,”-”)返回字符串11,substrbefore(“2009-11-01”, “-”)返回2009,substrafter(“11-01”, “-”)返回01,可以根据自己的需要构造一些自定义函数。
最后简单介绍一下awk中的数组应用。Awk是一种类似自解释语言,其中的数据应用可以很好的完成数据统计工作。Awk中数组使用无需提前声明。数组使用前,不必定义,也不必指定数组元素个数。经常使用循环来访问数组。下面是一种循环类型的基本结构
For (element in array ) print array[element]
Awk的特性介绍完了,现在开始实战了!
这个小工具的要求是这样的,在做数据割接时,将数据库的数据按照路由规格利用数据库提供的批量导出工具把数据导出成dat文件,需要对数据库的日志进行统计分析,看看每个分库每个分表导出多少条记录!
SQL3104N The Export utility is beginning to export data to file
"./PISADB10/CONTACTINFO977_1_3717.dat".
中间有若干行无关紧要的内容
Number of rows exported: 24040
其中的./PISADB10/CONTACTINFO977_1_3717.dat和Number of rows exported: 24040为关键信息。
PISADB10表示分库10,CONTACTINFO977_1_3717.dat表示分表1。而Number of rows exported: 24040为导出多少行。而日志文件里有上万行这样的日志,而且大概有100多个日志,要求这个小功能能够做到批量统计。
首先使用grep命令将相关重要信息提取出来,以免其他信息造成干扰。使用命令
Grep –E ‘^Number of rows exported|^".*(CONTACTINFO|DS_CLIENT_MAPPING|GROUPCONTACTMAP)\w*.dat’ db2export_1.sh.log
命令输出格式为:
。。。
"./PISADB5/CONTACTINFO472_1_12012.dat".
Number of rows exported: 40
"./PISADB5/CONTACTINFO473_1_12033.dat".
Number of rows exported: 24040
"./PISADB5/CONTACTINFO474_1_12054.dat".
Number of rows exported: 40
"./PISADB5/CONTACTINFO475_1_12075.dat".
Number of rows exported: 40
"./PISADB5/CONTACTINFO476_1_12096.dat".
Number of rows exported: 40
以上信息才是我想要的。将结果交给awk来处理完成,由于awk本身的机制,awk只能按行来处理文件,一次只能处理一行,所有以上信息处理还需要一些特殊的适配。Awk一次只读一样,如果该行不包含Number说明该行为分库和分表信息行,将其暂存起来,等到下一行Number行时,再对其进行处理。这里可以使用awk中的条件操作符if语句来完成,{if($0!~/Number/) tmp=$0} {if($0~/Number/)print “This is a number line and it’s owner is ”tmp},其中if的条件判断无需用()引起来,但是未避免语句错误最好将所有的条件判断都用()扩起来。!~为不匹配~为匹配,//之间可以输入正则表达式。
当行为number行时,需要将其中的关键数字截取出来做汇总。
if($0!~/Number/){
num=substrafter($0,” exported: ”)
}
这样就将num截取出来了,剩下的就是做汇总信息了!完整的脚本如下:
grep -E '^Number of rows exported|^".*(CONTACTINFO|DS_CLIENT_MAPPING|GROUPCONTACTMAP)\w*.dat' db2export_1.sh.log | awk '
function substrmid(src, begin, end) {
startindex=index(src,begin) + length(begin)
temp=substr(src,startindex)
endindex=index(temp,end) - length(end)
return substr(src,startindex,endindex)
}
function substrbefore(src, end) {
endindex=index(src,end)-length(end)
return substr(src,0,endindex)
}
function substrafter(src, begin) {
startindex=index(src,begin) + length(begin)
return substr(src,startindex)
BEGIN {
flag=""
total=0
table=""
}
{
if($0!~/Number/) {
flag=$0
}
}
{
if($0~/Number/) {
print $0
num=substrafter($0,"exported: ")
partdb=substrmid(flag, "/", "/")
fromtable=substrmid(flag, "_", "_")
P[partdb]=P[partdb]+num
S[fromtable]=S[fromtable]+num
total=total+num
}
}
END {
print "Total number of export: "total
for(p in P){
print p" number of export: "P[p]
}
for(s in S){
print "parttable "s" number of export: "S[s]
}
}