文本处理工具-AWK

awk简介

awk功能与sed相似,都是用来进行文本处理的。awk可以自动地搜索输入文件,并把每一个输入行切分成字段。许多工作都是自动完成的,例如读取每个输入行、字段分割。

awk工作原理

awk一次从文本内容中读取一行文本,按输入分隔符进行切,也可以使用-F选项指定分隔符,切成多个组成部分,将每段内容直接保存在内建的变量中$1,$2,$3....$NF(最后一列),引用指定的变量,可以显示指定断,或者多个断。如果需要显示全部的,需要使用$0来引用。可以对单个片断进行判断,也可以对所有断进行循环判断。其默认分隔符为空格

基础应用示例

选项:
-F 指明输入时用到的字段分隔符
-v var=value: 自定义变量

awk最常用的格式为:awk [options] 'pattern{action}' file
最常用的action是:print
一个示例:

[root@yufu ~]# df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda2       4.8G  1.6G  3.0G  35% /
/dev/sda1       190M   37M  144M  21% /boot
/dev/sda3       2.8G  1.1G  1.6G  39% /home

处理:awk可以处理多列

[root@yufu ~]# df -h | awk '{print $1}'
Filesystem
/dev/sda2
/dev/sda1
/dev/sda3
[root@yufu ~]# df -h | awk '{print $1,$5}'
Filesystem Use%
/dev/sda2 35%
/dev/sda1 21%
/dev/sda3 39%

$0与$NF,NF,$NF-1
$0整行输出

[root@yufu ~]# df -h | awk '{print $0}'
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda2       4.8G  1.6G  3.0G  35% /
/dev/sda1       190M   37M  144M  21% /boot
/dev/sda3       2.8G  1.1G  1.6G  39% /home

$NF的尾列输出

[root@yufu ~]# df -h | awk '{print $NF}'
on
/
/boot
/home

NF是统计每一行的总列数

[root@yufu ~]# df -h | awk '{print NF}'
7
6
6
6

$(NF-1)倒数第二列

[root@yufu ~]# df -h | awk '{print $1,$(NF-1)}'
Filesystem Mounted
/dev/sda2 35%
/dev/sda1 21%
/dev/sda3 39%

自定义插入字符

在输出文本列中,还可以添加自己要插入的内容

默认输出的字段之间间隙比较小,可以在字段键插入tab键分开距离

[root@yufu ~]# df -h | awk  '{print $1"\t"$2}'
Filesystem	Size
/dev/sda2	4.8G
/dev/sda1	190M
/dev/sda3	2.8G

也可以是输出内容更具可读性

[root@yufu ~]# df -h | awk  '{print $1" 磁盘大小:"$2}'
Filesystem 磁盘大小:Size
/dev/sda2 磁盘大小:4.8G
/dev/sda1 磁盘大小:190M
/dev/sda3 磁盘大小:2.8G

awk两种特殊模式

BEGIN和END是awk的两种特殊模式
BEGIN模式指定了处理文件前要执行的操作
END模式是指处理完所有行后要执行的操作

下面用一个示例来看:在磁盘使用率上加上一行标题,最后一行加上end标识

[root@yufu ~]# df -h | awk 'BEGIN{print "this is disk usage status:"} {print $1"\t"$5}'
this is disk usage status:
Filesystem	Use%
/dev/sda2	35%
/dev/sda1	21%
/dev/sda3	39%
[root@yufu ~]# df -h | awk 'BEGIN{print "this is disk usage status:"} {print $1"\t"$5} END{print "end"}'
this is disk usage status:
Filesystem	Use%
/dev/sda2	35%
/dev/sda1	21%
/dev/sda3	39%
end

awk分隔符

awk的分隔符分两种:“输入分隔符”和“输出分隔符”:
输入分隔符 : 简称 FS;默认的输入分隔符是空格,awk默认以空格为分隔符将文件进行列的分割。

输出分隔符 简称OFS;awk将文本切割处理后输出在屏幕上,以什么字符作为字段的分隔符?默认也是为空白字符最为输出分隔符。

在处理文本时,如果文本中没有空白字符那怎么进行分割?这时需要使用输入分隔符指定其他的字符作为分隔符进行切片处理,其实这里的 FS和上面示例中使用的-F作用是一样的,都是指定输入的分割符,同时,FS定义的分隔符可以在Action中进行引用作为输出时插入的内容
使用FS指定分隔符

[root@yufu ~]# awk -v FS=':' '{print $1,$3}' /etc/passwd | head -n 3
root 0
bin 1
daemon 2
[root@yufu ~]# awk -v FS=':' '{print $1FS$3}' /etc/passwd | head -n 3
root:0
bin:1
daemon:2

awk处理输出的内容默认是以空白字符作为列的分隔符,如果要指定输出内容的分割符可以使用OFS来指定,一个列子对比

[root@yufu ~]# df -h | awk '{print $1,$5}'
Filesystem Use%
/dev/sda2 35%
/dev/sda1 21%
/dev/sda3 39%
[root@yufu ~]# df -h | awk -v OFS=------ '{print $1,$5}'
Filesystem------Use%
/dev/sda2------35%
/dev/sda1------21%
/dev/sda3------39%

awk变量

内置变量

在上面的示例中已经使用到了变量,awk变量分为“内置变量”和“自定义变量”,示例中用到的NF、FS、OFS等就是awk的自定义变量,内置变量是awk预置好的,而自定义变量需要用户自己定义。

awk常用的内置变量如下:
FS:输入字段分隔符,默认为空白字符
OFS:输出字段分隔符,默认为空白字符
RS:输入记录的分隔符(换行符),指定输入时的换行符
NF:当前行的字段数(被分割成的列数),$NF 为最后一列,两者不同
NR:行号,当前处理文本的行号(也可以使用NR指定输出哪一行)
FNR:各文件分别计数的行号
FILENAME:当前文件名
ARGC:命令行参数的个数
ARGV:数组,保存的是命令行所给定的各个参数

上面已经写了FS和OFS的使用,接着看看RS,RS直白理解就是定义文本内容中以什么作为行的分隔符,有时候会用到这种方式定义行的分割
一个列子:fstab文件以 双引号 作为行的分隔符

[root@yufu opt]# cat /etc/fstab
UUID="6ec772b7-9efb-46d0-80a5-4deaf6d6efe0"	/boot	ext4	defaults	0 0
UUID="96cb6b8f-c3da-41d1-a063-cfd0e8177085"	/	ext4	defaults	0 0
UUID="6c323417-2b44-48fd-a4b3-074085b5fe95"    /home	ext4	defaults	0 0
UUID="fc02879d-56bb-4153-8fa9-c21aa9af8b19"	swap	swap	defaults	0 0
[root@yufu opt]# cat /etc/fstab | awk -v RS='"' '{print $1}'
UUID=
6ec772b7-9efb-46d0-80a5-4deaf6d6efe0
/boot
96cb6b8f-c3da-41d1-a063-cfd0e8177085
/
6c323417-2b44-48fd-a4b3-074085b5fe95
/home
fc02879d-56bb-4153-8fa9-c21aa9af8b19
swap

ORS指定输出分隔符
OFS可以指定输出时字段间用什么样的字符作为分隔符。默认为空格,这个功能与OFS作用差不多,个人感觉没有OFS好用。两个例子对比
ORS输出分割(乱遭遭的,看不懂)

[root@yufu opt]# df -h | awk -v ORS='----' '{print $1,$2}'
Filesystem Size----/dev/sda2 4.8G----/dev/sda1 190M----/dev/sda3 2.8G

OFS输出分割

[root@yufu opt]# df -h | awk -v OFS=---- '{print $1,$2}'
Filesystem----Size
/dev/sda2----4.8G
/dev/sda1----190M
/dev/sda3----2.8G

NF和$NF的用法
稍不注意会被这两个给搞混了,NF是统计一行中的列数,而$NF是取一行中的最后一列
一个例子对比

[root@yufu opt]# cat /etc/fstab 
UUID="6ec772b7-9efb-46d0-80a5-4deaf6d6efe0"	/boot	ext4	defaults	0 0
UUID="96cb6b8f-c3da-41d1-a063-cfd0e8177085"	/	ext4	defaults	0 0
UUID="6c323417-2b44-48fd-a4b3-074085b5fe95"    /home	ext4	defaults	0 0
UUID="fc02879d-56bb-4153-8fa9-c21aa9af8b19"	swap	swap	defaults	0 0
[root@yufu opt]#  awk '{print NF,$NF}' /etc/fstab 
6 0
6 0
6 0
6 0

每行以空格分割,一个6列。最后一列是0

NR行号处理
NR可以用来显示处理行的行号,也可以用来匹配指定行的内容

示例

[root@yufu opt]# df -h | awk '{print NR,$0}'
1 Filesystem      Size  Used Avail Use% Mounted on
2 /dev/sda2       4.8G  1.6G  3.0G  36% /
3 /dev/sda1       190M   37M  144M  21% /boot
4 /dev/sda3       2.8G  1.1G  1.6G  39% /home

指定行号处理

[root@yufu opt]# df -h | awk 'NR==2 {print $1,$5}'
/dev/sda2 36%

指定行号输出

[root@yufu opt]# df -h | awk 'NR==2'
/dev/sda2       4.8G  1.6G  3.0G  36% /

FNR:用于分别显示两个文件行号

[root@yufu opt]# awk '{print FNR,$0}' fi.txt fr.txt
1 hfjg
2 jsklyhfg
3 kifg
1 jkdfgufgjk
2 sdfg

ARGV内置变量表示的是一个数组,这个数组中保存的是命令行所给定的参数,数组的下标从0开始
一个列子

[root@yufu opt]# awk 'BEGIN{print "aaa",ARGV[1]}' fi.txt fr.txt
aaa fi.txt
[root@yufu opt]# awk 'BEGIN{print "aaa",ARGV[0]}' fi.txt fr.txt
aaa awk
[root@yufu opt]# awk 'BEGIN{print "aaa",ARGV[2]}' fi.txt fr.txt
aaa fr.txt

自定义变量

自定义变量容易理解,就是我们自己定义的变量,定义的方法:
自定义变量: -v varname=value (变量名区分大小写)

[root@yufu opt]# awk -v myvar="gudaoyufu" 'BEGIN{print myvar}'
gudaoyufu

或者

[root@yufu opt]# awk  'BEGIN{myvar="gudaoyufu";print myvar}'
gudaoyufu

awk格式化输出

在输出时可以是内容一一些特定的格式输出,格式化输出则需要使用printf来指定,功能与print相似,只是这个可以支持格式化输出
格式化输出:printf “FORMAT” , item1, item2, ...
(1) 必须指定FORMAT
(2) 不会自动换行,需要显式给出换行控制符,\n
(3) FORMAT中需要分别为后面每个item指定格式符
格式符:与item一一对应

%c: 显示字符的ASCII码
%d, %i: 显示十进制整数
%e, %E:显示科学计数法数值
%f:显示为浮点数
%g, %G:以科学计数法或浮点形式显示数值
%s:显示字符串
%u:无符号整数
%%: 显示%自身

修饰符:

#[.#]:第一个数字控制显示的宽度;第二个#表示小数点后精度,%3.1f

-: 左对齐(默认右对齐) %-15s
+:显示数值的正负符号 %+d

printf示例

[root@yufu opt]# awk -F : '{printf "%-15s %-10d\n",$1,$3}' /etc/passwd | head -n 4
root            0
bin             1
daemon          2
adm             3
[root@yufu opt]# awk -F : '{printf "-15s UID:%-10d\n",$1,$31}' /etc/passwd | head -n 4
root            UID:0
bin             UID:1
daemon          UID:2
adm             UID:3

awk运算操作

上面有个示例:

[root@yufu opt]# awk 'NR==2 {print $1}' /etc/fstab 
UUID="96cb6b8f-c3da-41d1-a063-cfd0e8177085"

这个就使用到了运算符,“NR==2”,获取一个行并做处理,awk支持多种运算处理
算术操作符:

x+y, x-y, x*y, x/y, x^y, x%y
-x: 转换为负数
+x: 转换为数值

字符串操作符:没有符号的操作符,字符串连接

赋值操作符:

=, +=, -=, *=, /=, %=, ^=
++, --

比较操作符:
==, !=, >, >=, <, <=

逻辑操作符:与&&,或||,非!

简单示例

[root@yufu opt]# awk -F :  '$3>=600 {print $1,$3}' /etc/passwd
suzhou 600
yufu 601
guoguo 602
user2 604
user3 605
user4 606
user5 607
user6 608
user7 609
user8 610
user9 611
user10 612
[root@yufu opt]# awk -F :  '$3 <100 || $3 >=600 {print $1,$3}' /etc/passwd
root 0
bin 1
dbus 81
vcsa 69
postfix 89
sshd 74
oprofile 16
... ...
suzhou 600
yufu 601
guoguo 602

匹配磁盘使用情况

[root@yufu opt]# df -h|awk -F% '/^\/dev/{print $1}'|awk '$NF>=30{print $1,$5}'
/dev/sda2 36
/dev/sda3 39

awk的PATTERN匹配

PATTERN:根据pattern条件,过滤匹配的行,再做处理,PATTERN中可以使用正则表达式来进行文本匹配
,例如,匹配passwd中以user开头的用户

[root@yufu opt]# awk '/^user/{print $0}' /etc/passwd
user2:x:604:604::/home/user2:/bin/bash
user3:x:605:605::/home/user3:/bin/bash
user4:x:606:606::/home/user4:/bin/bash
user5:x:607:607::/home/user5:/bin/bash
user6:x:608:608::/home/user6:/bin/bash
user7:x:609:609::/home/user7:/bin/bash
user8:x:610:610::/home/user8:/bin/bash
user9:x:611:611::/home/user9:/bin/bash
user10:x:612:612::/home/user10:/bin/bash

上面这个功能实现了grep的匹配功能,但它的使用与grep还是有些区别的:

grep "正则表达式" /etc/passwd
awk '/正则表达式/ {print $0}' /etc/passwd

在awk使用正则表达式时特殊字符要使用转义符;如下:

[root@yufu opt]# awk '/\/bin\/bash$/ {print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
suzhou:x:600:600::/home/feng:/bin/bash
tomcat:x:501:501::/home/tomcat:/bin/bash
yufu:x:601:601::/home/yufu:/bin/bash
guoguo:x:602:602::/home/guoguo:/bin/bash
user2:x:604:604::/home/user2:/bin/bash
... ...

结构控制语句

在awk中我们同样可以if这样的语法进行条件判断,只是在awk中使用if判断条件是在一行中完成,下面的一个示例:查看使用率超过30的分区

[root@yufu ~]# df -h | awk  '/^\/dev\/sd/{if ($5>=30){print $1,$5}}'
/dev/sda2 36%
/dev/sda3 39%

如果判断条件不成立则进行下面的动作:

[root@yufu ~]# df -h | awk  '/^\/dev\/sd/{if ($5>=50){print $1,$5}else{print $1,$5}}'
/dev/sda2 36%
/dev/sda1 21%
/dev/sda3 39%

awk数组使用

数组的格式:
name[0]="xiaoming"
name[1]="laowang"
上列中name为数组名,[0],[1]为元素下标 ,“小明”为数组元素

定义并打印一个简单的数组

[root@yufu ~]# awk 'BEGIN {a[0]="gudao";a[1]="yufu";print a[0],a[1]}'
gudao yufu

打印数的下标

[root@yufu ~]# awk 'BEGIN {a[0]="gudao";a[1]="yufu";for (i in a){print i}}'
0
1

把数组的下标和元素一起打印

[root@yufu ~]# awk 'BEGIN {a[0]="gudao";a[1]="yufu";for (i in a){print i, a[1]}}'
0 yufu
1 yufu

数组遍历

数组遍历需要使用for循环来配合处理,下面以一个示例看看awk的数组遍历的使用:
示例:一个web日志文件有60多万条的记录,怎样统计日志里每个ip的访问次数呢?

[root@yufu ~]# head access_log 
192.168.30.6 - - [14/May/2018:14:21:41 +0800] "GET / HTTP/1.1" 200 18 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.21 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2"

方法一:

[root@yufu ~]# cat access_log | awk '{print $1}' | sort |uniq -c | sort -nr 
 295764 172.20.109.251
 170000 172.20.109.233
 100000 172.20.101.240
  39243 172.20.111.90
  16000 172.20.109.164
   8000 172.20.102.79
   6062 172.20.111.9
   6000 172.20.103.69
   4031 172.20.101.23
   4014 192.168.30.6
   4000 172.20.1.24
   2056 172.20.111.65
   2020 172.20.111.164
   2002 172.20.110.34
   2000 172.20.111.162
    400 172.20.75.66
     58 ::1
     10 192.168.30.1

方法二:用AWK数组遍历来统计

[root@yufu ~]# awk '{num[$1]++}END{for(i in num){print i,num[i]}}' access_log | sort -nr -k2
172.20.109.251	295764
172.20.109.233	170000
172.20.101.240	100000
172.20.111.90	39243
172.20.109.164	16000
172.20.102.79	8000
172.20.111.9	6062
172.20.103.69	6000
172.20.101.23	4031
192.168.30.6	4014
172.20.1.24	    4000
172.20.111.65	2056
172.20.111.164	2020
172.20.110.34	2002
172.20.111.162	2000
172.20.75.66	400
::1	58
192.168.30.1	10

理解上面的例子:
awk中使用的num[$1]++ :数组中将日志文件的第一列作为数组num的下标,awk在读取第一行时会读取到这个数组,此时数组是这样的:num[172.20.109.251]++,由于后边的++运算符,awk会将数字0自动赋值给num[172.20.109.251]然后再做++运算。
此时num[172.20.109.251]做++,也就是0+1=1
当在读到第二个172.20.109.251时,此时num[172.20.109.251]的值已经为1,再做一次—++运算就是1+1得到的值为2,就这样以此类推
最后的++运算的值是多少,也就意味着172.20.109.251这个ip被运算了多少次,也就意味着这个地址出现了多少次。
最后通过for循环把num数组的下标(ip)和出现的次数都打印出来

同样,可以利用awk数组统计tcp的连接数:

[root@centos ~]# netstat -ant | awk '/^tcp/{state[$NF]++}END{for(i in state){print i,"\t"state[i]}}'
TIME_WAIT 	19
CLOSE_WAIT 	1
ESTABLISHED 	5
LISTEN 	11

还可以统计当前系的80端口的连接数与远程连接的ip

[root@web ~]# netstat -nat | grep "ESTABLISHED" | awk '/^tcp/{state[$(NF-2)]++}END{for(a in state){print a "  " state[a]}}' | grep ':80'
192.168.19.47:80  4

posted on 2018-05-17 10:50  孤岛鱼夫  阅读(265)  评论(0编辑  收藏  举报