gawk介绍及高难语句分析

 

一、前言

gawk是GNU awk,是awk遵循开源的实现。而awk是一款功能强大的文本处理工具,这款软件自成体系,实际上就是一个用于处理文本的编程语言工具。

 

二、awk介绍

(一)awk语法

awk程序由一系列模式-动作对组成,写作 pattern { action }

awk程序把每一行看成一条记录,每一行又被分隔符分隔成不同的字段(或称域)。awk解释器会判断行中是否符合模式,如果符合模式则执行相应的动作。

语法格式

awk [options] ‘PATTERN{ACTION}FILE …

awk [options] -f  ‘script-file’ FILE …

常用选项

-F          
--field-separator
输入字段分隔符
-f program-file          
--file program-file
从指定文件中读取程序
-v varname=val          
--assign varname=val
自定义变量,BEGIN块可用

 

(二)内置变量

1、特殊变量

变量 说明 示例
$0 引用整行记录  
$1-$n 分别代表引用对应的字段  
NR 已输入的记录条目数  
NF 当前记录中字段个数 $NF引用最后一个字段值,$符号是字段引用操作符
FNR 当前输入文件的总行数  
RS 输入记录分隔符  
FS 输入字段分隔符  
ORS 输出记录分隔符  
OFS 输入字段分隔符  
FILENAME 当前输入文件的文件名  
ARGV 索引从0开始的命令行参数数组 awk -F: '/root/,/bin/ {print}' /etc/passwd /etc/group          
ARGV[0]是awk          
ARGV[ARGC-1]是/etc/group
ARGC 命令行参数个数,不包括选项和程序部分 上例中,参数总数为3          
awk '{print ARGV[0],ARGV[2],ARGC}' /etc/passwd /etc/group

2、自定义变量

可以在选项中定义,上面有介绍

awk -v a="hello" 'BEGIN{print a}'

模式-动作对中定义使用

awk -F: 'BEGIN{a="hello"}{print a,$1,$7}' /etc/passwd

awk -F: 'BEGIN{a="hello";print a}'

awk -F: '{a="hello";print a,$0}' /etc/issue

 

(三)数组

1、普通数组

array[index]

普通数组使用数字作为index的值,awk自动生成的索引从1开始。但是自定义就可以随意,例如:

awk 'BEGIN{a[5]="hello";print a[5]}'

数组索引为数字的,实际在引用的时候还是转换到了字符索引使用的。

2、关联数组

使用字符串作为数组的索引

awk 'BEGIN{a[test]="hello";print a[test]}'

3、数组元素测试

使用特殊操作符in可以用来测试数据是否是关联数组

if (val in array)

    print array[val]

4、遍历关联数组

for (val in array) { process array[val] }

例如 for (var in array) {print i,array[i]}

5、删除数组或元素

delete array[index] 删除数据元素

delete array 删除数组

 

(四)操作符

$ 引用操作符
! + - * / % ** ^ -val +val 算术运算符
= += -+ *= /= %= ^= **= ^= 赋值操作符
++ -- 自增、自减单目运算符
&& || 逻辑运算符
< <= > >= != == 关系运算符
字符连接符
exp1?exp2:exp3 三目运算符。注意这里是表达式
in 引用数组索引

 

(5)函数

1、内建函数  

函数名 格式 说明
length length([arg]) 返回参数的长度。如果参数没有给出,将默认使用$0。如果参数是个数组名,返回数组长度(见文本综合示例)
substr substr(string,begin[,length]) 返回从指定位置开始的子串。如果不给出长度,默认取到字符结束。开始位置必须从1开始
tolower tolower(string) 转换为小写
toupper toupper(string) 转换为大写
split split(string, array [, sep]) 以指定的分隔符(默认为FS)分隔字符串并保存在索引从1开始的数组中,并返回元素的个数。(综合示例中有例子)
mktime mktime(datespec) 返回指定时间的时间戳
strftime strftime([format [, timestamp[, utc-flag]]]) 格式化时间戳
systime systime() 返回时间戳

2、自定义函数

格式为

function name([parameter-list])    
{    
     body-of-function    
}

可以定义函数,放在脚本文件中,使用-f选项读入。

 

(六)模式 PATTERN {ACTION}

名称 说明
BEGIN 特殊模式。处理前执行一次,参见后文中的执行顺序
END 特殊模式。处理的最后执行,参见后文中的执行顺序
/regular expression/ 正则表达式的模式来匹配。          
使用egrep风格,即扩展正则表达式ERE。
relational expression 使用关系运算符的关系表达式作为匹配条件
pattern && pattern 同时满足所有模式的匹配
pattern || pattern 满足任意一个模式的匹配
pattern1 ? pattern2 : pattern3 三目运算,模式1为真使用模式2,为假使用模式3
(pattern)  
! pattern 模式取反的匹配
pattern1, pattern2 范围匹配,从模式1匹配行开始,到模式2匹配行为止

 

(七)动作 ACTION

1、表达式

基本的表达式都可以使用

2、控制语句

条件判断

if (condition) statement [ else statement ]

循环    
while (condition) statement    
do statement while (condition)    
for (expr1; expr2; expr3) statement

循环控制    
break    
continue

next 结束当前行处理,直接处理下一行

数组操作    

delete array[index]    
delete array    

for (var in array) statement

退出    
exit [ expression ] 结束awk操作并退出    

3、输入输出语句

(1)print

语法:print item1, item2, ...

还可以使用输出重定向和管道

print … >> file

print … | command

 

(2)printf

printf "format",arg1,arg2,…

常用格式符

格式符 说明
%c 字符
%d,%i 十进制
%o 无符号八进制
%e,%E 科学计数
%f 显示浮点数
%g,%G 科学计数或浮点数显示
%s 字符串显示
%u 无符号数
%% 百分号本身
%x %X 十六进制
#.# 数字值代表输出宽度.精度。字符串前面代表最小长度,后面代表最大长度
- 左对齐
+ 显示数字符号

常用转义字符

\n 换行
\t tab
\r 回车
\\ \" \/ 特殊字符转义

 

三、程序执行顺序

gawk执行程序的顺序是:

通过-v选项设定的变量赋值;

gawk编译程序成为一个内建形式;

开始执行BEGIN块代码

开始读取ARGV数组中的文件的每一个行,如果没有文件则读取标准输入

正常的文件处理完成后,则执行END块

 

四、举例

(一)打印数值

# awk 'BEGIN { print 042, 42, 0x42 }'

34 42 66

# awk 'BEGIN { printf "%d %d %d %o %x 0x%04X \n",042, 42, 0x42, 16, 34, 1203 }'    
34 42 66 20 22 0x04B3

(二)稀疏数组

# awk 'BEGIN{a[5]="hello";a[test]="world";print a[5],a[test],length(a)}'    
hello world 2

注意,这里用length函数返回数组元素的个数

# awk 'NR<2{lens=split($0,arr);printf "%s\ttotal=%d\t last=%s\n",$0,lens,arr[lens] }' /etc/issue    
CentOS release 6.5 (Final)    total=4     last=(Final)

注意:split函数的索引从1开始

 

(三)打印所有普通用户名称及其shell

# awk -F: '$3>=500{print $1,$7}' /etc/passwd

打印所有普通用户名称,且名称中不含有数字的,及其shell,要求格式化输出对齐,打印表头,表尾打印时间

# awk -F: 'BEGIN{printf "%-15s\t%-8s\t%-s\n","USERNAME","ID","SHELL"}$3>=500 && $1~/[[:alpha:]]+\>/{printf "%-15s\t%-8s\t%-s\n",$1,$3,$7}END{printf "%30s\n",strftime("%Y-%m-%d %H:%M:%S")}' /etc/passwd

格式打印

[ 分析 ]

BEGIN块最先执行,然后每行都会匹配$3>=500 && $1~/[[:alpha:]]+\>/,这是个条件表达式的逻辑与表达式组成的模式PATTERN,要求同时成立,即前面要求第三字段大于等于500的,同时要求第一字段只能使用字母且至少有一个字母组成的名字。满足这两个条件的行才会被处理,使用printf格式输出。最后打印表尾,使用了格式化输出的内置函数。

 

(四)自定义分隔符

这个功能非常有用,看示例。

使用正则表达式作为分隔符

# ifconfig eth0 | awk -F'[ :]+' '/inet addr/{print $4}'

自定义分隔符

 

(五)数组高级应用

在另一个机器上使用ab压力测试

# ab -c 400 -n 12000 http://172.16.0.200/index.html

本机使用netstat做状态统计

1、统计连接的状态 小试牛刀

# netstat -tan | awk 'BEGIN{printf "%-15s %s\n","STATE","COUNT"}/^tcp\>/{stat[$NF]++}END{for(val in stat){printf "%-15s %s\n",val,stat[val]}}'

状态统计

2、要求使用IP统计 增加难度

(1)采用选项重新定义FS

# netstat -tan | awk -F '[[:space:]:]+' 'BEGIN{printf "%-20s %s\n","IP","COUNT"}/^tcp\>/ && $(NF-3)~/.*\..*/ {stat[$(NF-3)]++}END{for(val in stat){printf "%-20s %s\n",val,stat[val]}}'

IP统计

[ 分析 ]

找几行数据分析一下

tcp        0      0 0.0.0.0:2049                0.0.0.0:*                   LISTEN      
tcp        0     52 172.16.0.200:22             172.16.0.100:1709           ESTABLISHED     
tcp        0      0 :::35084                    :::*                        LISTEN 
tcp        0      0 ::ffff:172.16.0.200:80      ::ffff:172.16.0.210:50236   TIME_WAIT

按照默认分隔符,很难一下从上面几行中直接提取IP地址。

默认FS其实就是[[:space:]]+,就是多个空白字符作为分隔符的。

那么我们只需要将FS改为[[:space:]:]+,增加一个分号即可。这样从右边看过来,每一行从右数第4个数据就是IP。注意这里是4,不是3,$NF取的是最右边的空(NULL),因为状态值TIME_WAIT等右侧还有空白字符。

那么从右边看过来状态值就是$(NF-1),然后遇到了大片空白字符,这不算数因为是分隔符,然后遇到的就是:右边的端口号,不需要,然后遇到了一个冒号或者多个冒号,这都是分隔符,再向右就是IP地址。

所以IP地址是$(NF-3),但是有IPV6地址:::等的干扰,所以用了模式匹配/^tcp\>/ && $(NF-3)~/.*\..*/,要求tcp作为行首,且词尾锚定,同时要求IP地址的字段至少有一个.点号,当然你可以使用IPV4的正则表达式匹配,就是写出来有点吓人。

IP统计分析

(2)采用split函数分隔处理

netstat -tan | awk 'BEGIN{printf "%-20s %s\n","IP","COUNT"}/^tcp\>/{lens=split($5,spe,":");stat[spe[lens-1]]++}END{for(val in stat){printf "%-20s %s\n",val,stat[val]}}'

split

 

[ 分析 ]

使用正常FS是以连续空白字符为分隔符的,遇到这样的地址比较头疼 ::ffff:172.16.0.210:50236 。

要再用split函数以:分号分隔,取倒数第二个,再次提醒,索引从1开始。split函数的返回值是切割得到元素的个数,所以spe数组的lens-1索引就是IP地址,然后把IP放在stat数组中作为关联数组索引。

stat[spe[lens-1]]++的意思是,如果地址是IP1,就stat[IP1]++,如果地址是IP2,就stat[IP2]++,用IP地址取出元素的值自增的。

最后和上例一样for循环打印出来。

为什么有空呢?看下图,你就会明白。原来使用split函数切割:::*的时候,倒数第二个是一个空NULL。

NULL分析

问题找到了,改进一下语句试一试

# netstat -tan | awk 'BEGIN{printf "%-20s %s\n","IP","COUNT"}/^tcp\>/{lens=split($5,spe,":");if(length(spe[lens-1])) stat[spe[lens-1]]++}END{for(val in stat){printf "%-20s %s\n",val,stat[val]}}'

去除NULL

ok,问题解决

 

3、要求按照IP和状态统计 来个挑战

# netstat -tan | awk -F '[[:space:]:]+' 'BEGIN{printf "%-30s %s\n","IP","COUNT"}/^tcp\>/ && $(NF-3)~/.*\..*/ {stat[$(NF-3),$(NF-1)]++}END{for(val in stat){printf "%-30s %s\n",val,stat[val]}}'

挑战

 

 

(六)最后做一个有趣的分析,来个思维的小小挑战

有下面三条语句来分析一下,NF是你想象的数字吗?

# echo "acadbbafdbaabbddabb  " | awk -F "[ab]+" '{for(i=1;i<=NF;i++)print $i }END{print "NF="NF}'  
# echo "acadbbafdbaabbddabb" | awk -F "[ab]+" '{for(i=1;i<=NF;i++)print $i }END{print "NF="NF}'   
# echo "acadbbafdbaabbdd" | awk -F "[ab]+" '{for(i=1;i<=NF;i++)print $i }END{print "NF="NF}'

NF挑战

 

 

参考资料

官方资料

http://www.gnu.org/software/gawk/manual/gawk.html

你可能感兴趣的:(gawk,高难语句分析)