(转)awk 详解
出处:https://blog.51cto.com/yijiu/1358416
awk详解
awk是一款非常牛逼的报告生成工具,能够将文本格式化成显示为比较直观的结果
废话不多说,直接上例子
awk的内置变量
FS: field separator,读取文件本时,所使用字段分隔符;
RS: Record separator,输入文本信息所使用的换行符;
OFS: Output Filed Separator: 输出的字段分隔符,默认为空格;
ORS:OutputRow Separator:输出的记录分隔符,默认为新行;
BEGIN和END的作用是给程序赋予初始状态和在程序结束之后执行一些扫尾的工作,一般在语句之前执行可使用BEGIN来事先声明
BEGIN:一定是在真正处理文本之前做准备工作的,在这里我们可以定义其变量或其他操作,而END将在语句执行的最后执行我们定义的规则,如下所示:
#事先声明,先打印信息"UserName Shell",并将第一列和最后一列打印出来
[root@test3 ~]# awk -F: 'BEGIN{print "UserName Shell"}{print $1,$NF}' /etc/passwd | head -3
UserNameShell
root /bin/bash
bin /sbin/nologin
END模式
#首先变量BEGIN事先定义了将默认分隔符为冒号(FS=":") 输出显示的分隔符字段也是冒号(OFS=":"),并打印文件中的第一列和最后一列,打印完毕之后,最最后一行显示内容”END”
[root@test3 ~]# awk'BEGIN {FS=":";OFS=":"} {print $1,$NF} END{print"end"}' /etc/passwd | tail -3
mysql:/bin/bash
mockbuild:/bin/bash
end
如果不指定OFS变量值,其输出的分隔符则为空格,如下所示
#可以发现,打印输出的分隔符为空格,也就是默认的
[root@test3 ~]# awk'BEGIN {FS=":"} {print $1,$NF} END{print "end"}'/etc/passwd | tail -3
mysql /bin/bash
mockbuild /bin/bash
end
正则表达式模式:
正则表达式模式可以将其找到文件当中匹配某行再对其做处理,如下所示
#指定默认分割符号为冒号(可以直接-F: 不用这么麻烦)
#指定默认分隔符显示为冒号
#找到匹配我们事先定好好的以sh结尾的行,并打印列首和列尾,最后以显示End收尾
[root@test3 ~]# awk 'BEGIN{print "UserName Shell"}{FS=":";OFS=":"} /sh$/{print $1,$NF} END {print "End"}' /etc/passwd
UserName Shell
root:x:0:0:root:/root:/bin/bash:root:x:0:0:root:/root:/bin/bash
test:/bin/bash
test2:/bin/bash
mysql:/bin/bash
mockbuild:/bin/bash
End
做多行匹配
[root@test3 ~]# awk -F':' 'BEGIN {print "UserNameShell"} {OFS=":"} /sh$/,/in$/ {print$1,$NF} END {print "end"}' /etc/passwd
UserName Shell
root:/bin/bash
bin:/sbin/nologin
test:/bin/bash
test2:/bin/bash
mysql:/bin/bash
mockbuild:/bin/bash
end
awk判断模式
我们想显示用户的id号大于或等于100,并打印
#使用冒号作为分隔符,对$3进行判断,如果其值大于100,那么将列首和列尾打印
[root@test3 ~]# awk -F: '$3>100 {print $1,$NF }' /etc/passwd
abrt /sbin/nologin
saslauth /sbin/nologin
test /bin/bash
test2 /bin/bash
mysql /bin/bash
mockbuild /bin/bash
使用awk进行匹配
#以冒号为分隔符,如果第7列内容出现bash字段(这样好理解),那么将第1列和第3列打印
[root@test3 ~]# awk -F: '$7~/bash/ {print $1,$3}' /etc/passwd
root 0
test 500
test2 501
mysql 498
mockbuild 502
取反
输出非bash用户
[root@test3 ~]# awk -F ':' '$7!~/bash/ {print $1,$NF}'/etc/passwd
printf
语法:printfformat, item1, item2, ...
要点:
1、其与print命令的最大不同是,printf需要指定format;
2、format用于指定后面的每个item的输出格式;
3、printf语句不会自动打印换行符;\n
format格式的指示符都以%开头,后跟一个字符;如下:
%c: 显示字符的ASCII码;
%d, %i:十进制整数;
%e, %E:科学计数法显示数值;
%f: 显示浮点数;
%g, %G: 以科学计数法的格式或浮点数的格式显示数值;
%s: 显示字符串;
%u: 无符号整数;
%%: 显示%自身;
修饰符:
N: 显示宽度;
-: 左对齐;
+:显示数值符号;
#打印第1列和第3列,并使用修饰符:字符距离为15,类型为整数型并将其换行显示,如果不加参数\n 那么打印出的信息则全显示在一行中
[root@test3 ~]# awk -F: '{printf "%-15s%i\n",$1,$3}' /etc/passwd | head -5
root 0
bin 1
daemon 2
adm 3
lp 4
这里面有2个符号 一个是s% 一个是 i% s%表示显示的是字符串 i%表示显示为十进制的整数 %-15s %i\n 表示使用多宽的字符来显示它 -号表示左对齐
不使用右对齐
[root@test3~]# awk -F: '{printf "%15s %i\n",$1,$3}' /etc/passwd | head 2
root 0
bin 1
显示无符号整数:
[root@test3~]# awk -F: '{printf "%15s %u\n",$1,$3}' /etc/passwd
要点:
1、各项目之间使用逗号隔开,而输出时则以空白字符分隔;
2、输出的item可以为字符串或数值、当前记录的字段(如$1)、变量或awk的表达式;数值会先转换为字符串,而后再输出;
3、print命令后面的item可以省略,此时其功能相当于print $0, 因此,如果想输出空白行,则需要使用print"";
awk的操作符
·算数操作符
-x: 负值
+x: 转换为数值;
x^y:
x**y: 次方
x*y: 乘法
x/y:除法
x+y:
x-y:
x%y:
·字符串操作符:
只有一个,而且不用写出来,用于实现字符串连接;
·赋值操作符:
=
+=
-=
*=
/=
%=
^=
**=
++
--
需要注意的是,如果某模式为=号,此时使用/=/可能会有语法错误,应以/[=]/替代;
·布尔值
awk中,任何非0值或非空字符串都为真,反之就为假;
·比较操作符:
x <y True ifx is less than y.
x <= y Trueif x is less than or equal to y.
x > y Trueif x is greater than y.
x >= y Trueif x is greater than or equal to y.
x == y Trueif x is equal to y.
x != y Trueif x is not equal to y.
x ~ y Trueif the string x matches the regexp denoted by y.
x !~ y Trueif the string x does not match the regexp denoted by y.
subscript in array True if the array array hasan element with the subscript subscript.
·表达式间的逻辑关系符:
&&
||
比如:
#如果第3列的数值大于400 并且第7列匹配bash字符串那么打印1 3 7列 并修饰
[root@test3~]# awk -F ':' '$3>400 &&$7~/bash/{printf "%-15s %i %s\n",$1,$3,$7}' /etc/passwd
test 500 /bin/bash
test2 501 /bin/bash
mysql 498 /bin/bash
mockbuild 502 /bin/bash
awk常用分隔符
一般常用分隔符分为两类:
(1)字段分隔符:某行中如何区分不同的列
(2)行分隔符:默认情况下就是换行符,完全指定别的分割符为换行符,这样可以将一行当多行或 多行当一行来处理
所以一般来将字段分隔符的输入分割符,我们被称为FS,而FS恰好就是awk内置变量
上面已经介绍了awk的内置变量FS和OFS的使用,接下来我们来回顾一下
FS:指定输入字段分隔符
#指定以冒号为分割符号,打印列首和列尾
[root@test3~]# awk 'BEGIN{FS=":"} {print $1,$NF}' /etc/passwd | head -3
root/bin/bash
bin/sbin/nologin
daemon/sbin/nologin
OFS:指定输出字段分割符
#指定以冒号为分割符号,指定##为默认输出分割符(这里不再是空格),打印列首和列尾
[root@test3~]# awk 'BEGIN{FS=":";OFS="##"} {print $1,$NF}' /etc/passwd| head -3
root##/bin/bash
bin##/sbin/nologin
daemon##/sbin/nologin
NF
#引用变量本身的值是不可以加$的 这里加$是因为用变量本身的函数再补以$符号
[root@test3~]# df -h | awk 'BEGIN {print "fliesystem mount" } {print $1,$NF}'
加$符号,打印出的结果,筛选为第5列
[root@test3~]# awk -F ':' 'BEGIN{TEST=5} {print $TEST}' /etc/passwd | head -5
root
bin
daemon
adm
lp
再将$符号去掉可以看到只将TEST赋予的值打印了出来
[root@test3~]# awk -F ':' 'BEGIN{TEST=5} {print TEST}' /etc/passwd | head -5
5
5
5
5
5
控制语句
if-else
语法:if(condition) {then-body} else ` else-body `
我们可以使用单分支if语句 只有if没有else
#判断,如果这个用户的id号是否为0,如何是0则输出为管理员反之并打印其用户名
[root@test3~]# awk -F: '{if($3==0){print $1,"admin"}else{print$1,"user"}}' /etc/passwd | head -3
rootadmin
binuser
daemonuser
使用修饰符
[root@test3~]# awk -F: '{if($3==0){printf "%-15s%s\n",$1,"admin"}else{printf "%-15s %s\n",$1,"user"}}' /etc/passwd | head -3
root admin
bin user
daemon user
用户的自定义变量
参数:-v
#如果用户的id号大于等于500那么使变量在其自身+1次并打印,最终所有大于500的用户的个数的和
#print必须写在END后面,不然会依次显示行的处理结果
[root@test3~]# awk -F: -v sum=0 '{if($3>500) sum++} END {print sum}' /etc/passwd
2
这里sum没有加$,因为$显示的变量值是对应的字段的值,如下所示:
[root@test3~]# awk -F: -v sum=0 '{if ($3>500) sum++} END{print $sum}'/etc/passwd
x
分别打印id大于500的用户名
[root@test3~]# awk -F: '{if ($3>=500) print $1}' /etc/passwd
test
test2
mockbuild
使用awk统计登录失败的ssh登录信息
#首先找到关键字,其实用grep+管道+awk更直观一些,这里为了练习就全部都用awk了
#看到有3行此类信息,然后对其统计有多少条匹配的信息
[root@test3~]# awk '/Failed/' /var/log/secure
Feb 1116:48:40 test3 sshd[2015]: Failed password for invalid user adad from 10.0.10.1port 58136 ssh2
Feb 1116:48:44 test3 sshd[2015]: Failed password for invalid user adad from 10.0.10.1port 58136 ssh2
Feb 1116:48:47 test3 sshd[2015]: Failed password for invalid user adad from 10.0.10.1port 58136 ssh2
#使用-v声明变量值为0,{sum++}必须单独在一{..}模块中,在结尾处打印变量
[root@test3~]# awk -v sum=0 '/Failed/ {sum++} END {print sum}' /var/log/secure
3
筛选web日志文件,并且将以GET请求后缀名是以.html结尾的行进行访问数统计
[root@test3logs]# awk '{if($7 ~ "html$")print $0}' access_www.test.com.log-20140209
10.0.10.1- - [04/Feb/2014:10:40:07 +0800] "GET /1.html HTTP/1.1" 200 6
10.0.10.1- - [04/Feb/2014:10:40:11 +0800] "GET /2.html HTTP/1.1" 200 5
10.0.10.62- - [04/Feb/2014:10:40:37 +0800] "GET /1.html HTTP/1.1" 200 6
10.0.10.62- - [04/Feb/2014:10:40:41 +0800] "GET /2.html HTTP/1.1" 200 5
10.0.10.62- - [04/Feb/2014:10:40:45 +0800] "POST /2.html HTTP/1.1" 200 5
10.0.10.62- - [04/Feb/2014:10:40:51 +0800] "POST /1.html HTTP/1.1" 200 6
10.0.10.1- - [04/Feb/2014:10:48:22 +0800] "GET /1.html HTTP/1.1" 200 6
可以看到一共有7行,下面来使用awk来统计其访问量
[root@test3logs]# awk -v sum=0 '{if($7 ~ "html$") sum++} END {print sum}'access_www.test.com.log-20140209
7
循环
while循环
主要功能在切片后每一个字段中进行循环
语法:while (condition) {statement1;statement2;...}
#显示没行的前三段
[root@test3logs]# awk -F: '{i=1;while (i<=3) {print $i;i++} }'/etc/passwd | head -6
root
x
0
bin
x
1
#引用变量不需要加$
#每个处理结果都是一行,所以是按行输出的
#如果不想让其按行输出则:
[root@test3logs]# awk -F: '{i=1; while(i<=3) {printf "%s ", $i;i++};{printf"\n"} }' /etc/passwd
awk内置函数length
length能取出指定字符的长度
#显示每行中数字大于等于100的数字
[root@test3~]# cat hello.txt
111 11 234 99 87
130 8328 91
2384842 84 671 24
87 62 1992
#之所以使用i<=NF是因为每个字段的数值是不一样的,但是这里每一列的个数都不一样的,为的目的是便利每一行的每一列
[root@test3~]# awk '{i=1;while (i<=NF) {if($i>=100) print $i;i++ }}' hello.txt
111
234
130
2384
842
671
1992
do-while 循环
其与while区别在于 while有可能一开始就不会循环,因为条件不满足
而do-while则不管值为真假(不管条件满足与否,至少先执行一次循环),先进行循环
语法: do {statement1, statement2, ...} while (condition)
awk -F:'{i=1;do {print $i;i++}while(i<=3)}' /etc/passwd
awk -F:'{i=4;do {print $i;i--}while(i>4)}' /etc/passwd
for循环
语法 : for (variable assignment; condition iteration process ) {statement1 ... }
#以冒号为分割符,显示每行的前三列,与上面的while循环的结果是一样的
[root@test3~]# awk -F: '{for(i=1;i<=3;i++) print $i}' /etc/passwd | head -10
root
x
0
bin
x
1
daemon
x
2
使用内置函数length
#awk-F: '{for(i=1;i<=NF;i++) { if (length($i)>=4) {print $i}}}' /etc/passwd
使用for循环来查找这个文件中大于等于100的数字
#一定让i小于NF,如果$i大于等于100 那么现实$i
#大致意思为我们要读取这个文件的每一行,而后进行每一列进行判断,而for是对其进行每一列来进行循环的,if为判断每一列只要大于其值则打印
[root@test3~]# awk '{for(i=1;i<=NF;i++) {if ($i>=100) print $i}}' hello.txt
111
234
130
2384
842
671
1992
数组
#数组为一组连续的课存多个值内存空间
#使用数组索引(下标)
#例:
bash:
arry=('mon''tue' 'wed')
arry[2]
这里的arry[2]就为数组的索引,但是bash4之后还支持关联数组:
索引下标可以非数组,可以自定义下标:
arry=(a='mon'b='tue')
aryy[b]
而awk是支持所谓的关联数组的
for能够去便利数组元素:
语法: for (i in array) {statement1,statement2, ... }
统计passwd文件最后一列出现的次数
[root@test3~]# awk -F: '$NF!~/^$/{BASH[$NF]++}END{for (A in BASH) {printf"%-15s:%i\n" ,A,BASH[A]}}' /etc/passwd
/sbin/shutdown:1
/bin/bash :5
/sbin/nologin :22
/sbin/halt :1
/bin/sync :1
#首先$NF 为最后一个字段
#!~为不匹配,这里不为空
#如果最后一列不为空,则将其值取出并对其做匹配:
BASH[] 为数组 那么BASH[NF]为数组索引,可以将最后一个字段的字段本身,将其作为下标
比如:
最后一列为 /bin/bash,那么将其做为BASH的下标
所以会出现这样结果:
BASH[/bin/bash]
BASH[/sbin/nologin] 用它来做下标即为另一个元素,那么这个元素中存放的是什么也没有声明
所以这里BASH[$NF]++ 使其自身自动加值,一般初始值是0,所以 它的意思为 如果不为空,则BASH[NF]++ :
当我们读了第一行之后意味着BASH[/bin/bash]的元素为=1 ,
再读取第二行于是又一个新元素其BASH[/sbin/nologin]的值=1
再读取第三行,筛取最后一列为/sbin/nologin其 BASH[/sbin/nologin]的值+1=2
继续。。。每一次都往上累加
#
{print "%15s:%i\n",A,BASH[A]}}
#显示了A,再显示了BASH[A]
A是字符串
BASH[A]为元素的值
得出的结果为:
/bin/bash : 1
/sbin/nologin:1 ....
#使用netstate -tanl查看网络状态并统计每种状态的值
[root@test3~]# netstat -tanl | awk '/^tcp/{sum[$NF]++} END {for (i in sum) {printi,sum[i]}}'
TIME_WAIT61
ESTABLISHED3
LISTEN8
语法说明:
awk /^tcp/{动作} #将tcp协议开头的行筛选并打印出来
awk /^tcp/{sum[$NF]} #$NF的值一定是字符串本身
awk /^tcp/{sum[$NF]++} #第一次状态值为1 第二次为2 以此类推
#for(下标 in 数组)
awk/^tcp/{sum[$NF]++}END{for(i in sum)} #使用for循环,会便利数组的下标,所以我们显示的是
awk/^tcp/{sum[$NF]++}END{for(i in sum) print i,sum[i]}' #i是字符串本身,sum[i] 数组的以i为下标的数组的值
所以得出的结果为:
[root@test3~]# netstat -tanl | awk '/^tcp/{sum[$NF]++} END {for (i in sum) {printi,sum[i]}}'
ESTABLISHED3
LISTEN8
统计当前系统上的日志access.log 每一个ip发起过多少次请求
[root@test3logs]# awk '{ip[$1]++} END {for(i in ip) print i,ip[i]}'access_www.test.com.log
127.0.0.161
next
提前结束对本行文本的处理,并接着处理下一行;例如,下面的命令将显示其ID号为奇数的用户:
[root@test3logs]# awk -F: '{if($3%2==0) next;print $1,$3}' /etc/passwd
bin 1
adm 3
sync 5
halt 7
operator11
gopher13
nobody99
dbus 81
vcsa 69
abrt173
saslauth499
postfix89
named25
test2501
awk内置函数
语法:split(string, array [,fieldsep [, seps ] ])
功能:将string表示的字符串以fieldsep为分隔符进行分隔,并将分隔后的结果保存至array为名的数组中;数组下标为从1开始的序列;
[root@test3logs]# netstat -tan | awk'/:80\>/{split($5,clients,":");ip[clients[4]]++}END{for(a in ip)print ip[a],a}' | sort -rn | head -50
9010.0.10.62
1 *
语法说明:
awk '/:80\>/
{split($5,clients,":"); #当我们取得第五列的时候,将第五列以冒号作为分割(":" 为分隔符),保存在名为clients的变量中,意味着clients1保存的是我们的ip地址,clients2保存的是端口号.....
IP[clients[1]]++} #ip地址+1
END{for(i in IP) #便利每个IP显示的次数
{print IP[i],i}}' #先显示次数再显示地址
筛取硬盘空间使用比例大于20%
[root@test3logs]# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda3 20G 13G 5.3G 72% /
tmpfs 498M 0 498M 0% /dev/shm
/dev/sda1 194M 45M 140M 25% /boot
/dev/mapper/myvg-mydata
5.0G 170M 4.6G 4% /mydata
[root@test3logs]# df -hl | awk'!/File/{split($5,percent,"%");if(percent[1]>=20) {print $1}}'
/dev/sda3
/dev/sda1