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 }