1 AWK高级应用 2 3 4 5 在进行数据割接时,需要对其导出脚本的日志文件进行统计分析以便核对是否有数据没有导出的情况!该日志文件的格式都是固定的,可以使用脚本来完成统计分析,而且脚本很灵活小巧。 6 7 8 9 因为其复杂的语法和不明确的错误提示,造成awk的使用者进而远之,awk整体上比较难掌握。Awk是一种自解释的编程语言。而awk强大的文本处理功能正好能够胜任该工作。如果要格式化报文或从一个大的文本文件中抽取数据包,那么awk可以完成这些任务。它在文本浏览和数据的熟练使用上性能优异。 10 11 12 13 14 15 Awk的特性网上有很多资料,大家可以参考一下。本次所运用的awk的特性有: 16 17 Awk数组、printf修饰符、内置字符串函数、awk操作符、条件操作符。 18 19 20 21 要想使用好awk,必须对awk的语法有一定的认识,否则你会被一大堆莫名其妙的错误所包围。 22 23 24 25 Awk语句都是由模式和动作组成。模式的包括俩个关键字,BEGIN和END。BEGIN模式在awk遍历文本前调用,用来打印一些头信息或是声明一些全局变量。例如: 26 27 28 29 Log.dat记录着日志信息,日志为日期-ip-手机号码。 30 31 linux:/home/dss/dss/logs/msgs> cat log.dat 32 33 20091111-172230-665-10.168.38.63-15035198115 34 35 20091111-172230-738-10.168.38.65-13840784654 36 37 20091111-172238-571-10.168.38.63-15929933330 38 39 20091111-172238-668-10.168.38.63-13666463997 40 41 20091111-172240-262-10.168.38.68-13591931301 42 43 20091111-172242-24-10.168.38.63-15041615076 44 45 20091111-172248-427-10.168.38.63-13409199466 46 47 48 49 50 51 如果我想统计来自ip10.168.38.63的日志有几个,命令如下: 52 53 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 54 55 IP LOG 56 57 10.168.38.63 20091111-172230-665-10.168.38.63-15035198115 58 59 10.168.38.63 20091111-172238-571-10.168.38.63-15929933330 60 61 10.168.38.63 20091111-172238-668-10.168.38.63-13666463997 62 63 10.168.38.63 20091111-172242-24-10.168.38.63-15041615076 64 65 10.168.38.63 20091111-172248-427-10.168.38.63-13409199466 66 67 68 69 其中头信息IP和LOG在遍历log.dat前打印! 70 71 而END模式在遍历完文件最后执行,可以用于信息汇总,或打印结束日志。 72 73 74 75 实际动作需要包含在{}之间,如果不指明,默认动作是打印所有行。 76 77 78 79 Awk的域和记录,awk执行时,其浏览域标记为$1,$2… $n。这种方法称为域标识。使用这些域标识将更容易对域进行进一步处理。$0为匹配整行,看个例子: 80 81 82 83 linux:/home/dss/dss/logs/msgs> awk -F'-' '{print $4, $5}' log.dat 84 85 10.168.38.63 15035198115 86 87 10.168.38.65 13840784654 88 89 10.168.38.63 15929933330 90 91 10.168.38.63 13666463997 92 93 10.168.38.68 13591931301 94 95 10.168.38.63 15041615076 96 97 10.168.38.63 13409199466 98 99 100 101 以上命令只打印第4列和第5列。并且域分隔符为-,默认的域分隔符为空格,使用-F参数指定域分隔符。 102 103 awk有许多内置变量用来设置环境信息。这些变量可以被改变。下面列出一些最常使用的一些变量,并给出其基本含义。 104 105 ARGC 命令行参数个数 106 107 ARGV 命令行参数排列 108 109 ENVIRON 支持队列中系统环境变量的使用 110 111 FILENAME awk浏览的文件名 112 113 FNR 浏览文件的记录数 114 115 FS 设置输入域分隔符,等价于命令行- F选项 116 117 NF 浏览记录的域个数 118 119 NR 已读的记录数 120 121 OFS 输出域分隔符 122 123 ORS 输出记录分隔符 124 125 RS 控制记录分隔符 126 127 128 129 awk有许多强大的字符串函数,这些字符串函数在处理文本方面起着很重要的作用。通过这些内置字符串处理函数可以很随意的定义一些自己想要的函数。下面为awk中内置字符串处理函数。 130 131 gsub (r, s) 在整个$0中用s替代r 132 133 gsub (r, s, t) 在整个t中用s替代r 134 135 index (s , t) 返回s中字符串t的第一位置 136 137 length ( s ) 返回s长度 138 139 match (s , r) 测试s是否包含匹配r的字符串 140 141 split (s , a , fs) 在f s上将s分成序列a 142 143 sprint (fmt , exp) 返回经f m t格式化后的e x p 144 145 sub (r, s) 用$ 0中最左边最长的子串代替s 146 147 substr (s , p) 返回字符串s中从p开始的后缀部分 148 149 substr (s , p , n) 返回字符串s中从p开始长度为n的后缀部分 150 151 152 153 如下我只要使用index和substr函数就可以组合出很强大的自定义函数: 154 155 function substrmid(src, begin, end) { 156 157 startindex=index(src,begin) + length(begin) 158 159 temp=substr(src,startindex) 160 161 endindex=index(temp,end) - length(end) 162 163 return substr(src,startindex,endindex) 164 165 } 166 167 168 169 function substrbefore(src, end) { 170 171 endindex=index(src,end)-length(end) 172 173 return substr(src,0,endindex) 174 175 } 176 177 178 179 function substrafter(src, begin) { 180 181 startindex=index(src,begin) + length(begin) 182 183 return substr(src,startindex) 184 185 } 186 187 188 189 自定义函数substrbefore(src, end)是截取src中end之前的字符串,substrmid(src, begin, end)是截取src中介于begin和end之间的字符串(不包含begin和end),substrend(src, end)是截取src中end之后的字符换,例如: 190 191 Substrmid(“2009-11-01”, “-”,”-”)返回字符串11,substrbefore(“2009-11-01”, “-”)返回2009,substrafter(“11-01”, “-”)返回01,可以根据自己的需要构造一些自定义函数。 192 193 194 195 最后简单介绍一下awk中的数组应用。Awk是一种类似自解释语言,其中的数据应用可以很好的完成数据统计工作。Awk中数组使用无需提前声明。数组使用前,不必定义,也不必指定数组元素个数。经常使用循环来访问数组。下面是一种循环类型的基本结构 196 197 For (element in array ) print array[element] 198 199 Awk的特性介绍完了,现在开始实战了! 200 201 202 203 这个小工具的要求是这样的,在做数据割接时,将数据库的数据按照路由规格利用数据库提供的批量导出工具把数据导出成dat文件,需要对数据库的日志进行统计分析,看看每个分库每个分表导出多少条记录! 204 205 206 207 SQL3104N The Export utility is beginning to export data to file 208 209 "./PISADB10/CONTACTINFO977_1_3717.dat". 210 211 中间有若干行无关紧要的内容 212 213 Number of rows exported: 24040 214 215 其中的./PISADB10/CONTACTINFO977_1_3717.dat和Number of rows exported: 24040为关键信息。 216 217 PISADB10表示分库10,CONTACTINFO977_1_3717.dat表示分表1。而Number of rows exported: 24040为导出多少行。而日志文件里有上万行这样的日志,而且大概有100多个日志,要求这个小功能能够做到批量统计。 218 219 220 221 首先使用grep命令将相关重要信息提取出来,以免其他信息造成干扰。使用命令 222 223 Grep –E ‘^Number of rows exported|^".*(CONTACTINFO|DS_CLIENT_MAPPING|GROUPCONTACTMAP)\w*.dat’ db2export_1.sh.log 224 225 命令输出格式为: 226 227 。。。 228 229 "./PISADB5/CONTACTINFO472_1_12012.dat". 230 231 Number of rows exported: 40 232 233 "./PISADB5/CONTACTINFO473_1_12033.dat". 234 235 Number of rows exported: 24040 236 237 "./PISADB5/CONTACTINFO474_1_12054.dat". 238 239 Number of rows exported: 40 240 241 "./PISADB5/CONTACTINFO475_1_12075.dat". 242 243 Number of rows exported: 40 244 245 "./PISADB5/CONTACTINFO476_1_12096.dat". 246 247 Number of rows exported: 40 248 249 250 251 以上信息才是我想要的。将结果交给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的条件判断无需用()引起来,但是未避免语句错误最好将所有的条件判断都用()扩起来。!~为不匹配~为匹配,//之间可以输入正则表达式。 252 253 254 255 当行为number行时,需要将其中的关键数字截取出来做汇总。 256 257 if($0!~/Number/){ 258 259 num=substrafter($0,” exported: ”) 260 261 } 262 263 这样就将num截取出来了,剩下的就是做汇总信息了!完整的脚本如下: 264 265 266 267 grep -E '^Number of rows exported|^".*(CONTACTINFO|DS_CLIENT_MAPPING|GROUPCONTACTMAP)\w*.dat' db2export_1.sh.log | awk ' 268 269 270 271 function substrmid(src, begin, end) { 272 273 startindex=index(src,begin) + length(begin) 274 275 temp=substr(src,startindex) 276 277 endindex=index(temp,end) - length(end) 278 279 return substr(src,startindex,endindex) 280 281 } 282 283 284 285 function substrbefore(src, end) { 286 287 endindex=index(src,end)-length(end) 288 289 return substr(src,0,endindex) 290 291 } 292 293 294 295 function substrafter(src, begin) { 296 297 startindex=index(src,begin) + length(begin) 298 299 return substr(src,startindex) 300 301 302 303 BEGIN { 304 305 flag="" 306 307 total=0 308 309 table="" 310 311 } 312 313 314 315 { 316 317 if($0!~/Number/) { 318 319 flag=$0 320 321 } 322 323 } 324 325 326 327 { 328 329 if($0~/Number/) { 330 331 print $0 332 333 num=substrafter($0,"exported: ") 334 335 partdb=substrmid(flag, "/", "/") 336 337 fromtable=substrmid(flag, "_", "_") 338 339 P[partdb]=P[partdb]+num 340 341 S[fromtable]=S[fromtable]+num 342 343 total=total+num 344 345 } 346 347 } 348 349 350 351 END { 352 353 print "Total number of export: "total 354 355 for(p in P){ 356 357 print p" number of export: "P[p] 358 359 } 360 361 for(s in S){ 362 363 print "parttable "s" number of export: "S[s] 364 365 } 366 367 }