细说awk

awk是一个处理文本的编程语言工具,能用简短的程序处理标准输入或文件、数据排序、计算以及生成报表等。

awk的基本语法:

awk option ‘pattern {action}’ file

awk选项

-f program-file    //从文件中读取awk程序源文件
-F fs                   //指定fs为输入字段分隔符
-v var=value      //变量赋值
--posix              //兼容POSIX正则表达式
--dump-variables=[file]    //把awk命令时的全局变量写入文件,默认文件是awkvars.out
--profile=[file]     //格式化awk语句到文件,默认是awkprof.out

awk模式

BENG{}              //给程序赋予初始状态,先执行的工作
END{}                //程序结束之后执行的一些结尾工作
/reqular expression/     //为每个输入记录匹配正则表达式
pattern && pattern       //逻辑and,满足两个模式
pattern || pattern          //逻辑or,满足其中一个模式
! pattern                      //逻辑not,不满足模式
pattern1,pattern2     //连续范围匹配

示例: 1)从文件读取awk程序处理文件

echo '{print $1}' > test.awk
tail -3 /etc/services |awk -f test.awk  //以下是过渡出的内容
iqobject
iqobject
matahari

2)指定输入分隔符,指定多个分隔符

tail -2 /etc/passwd|awk 'BEGIN{FS=":"}{print $1}'
tail -2 /etc/passwd|awk -F: -f test.awk
a="ass;gg#sow;p#slolow;wpw#pp"
echo $a|awk -F'[;#]+' '{print $3}'   //以下是显示的内容
sow

3)变量赋值

awk -v a=$a 'BEGIN{print a}'  //引用系统变量
ass;gg#sow;p#slolow;wpw#pp

awk 'BEGIN{print '$a'}'   //会报语法错误,是因为变量里含有特殊符号 

a=123
awk 'BEGIN{print '$a'}'
123
awk -v a=linux 'BEGIN{print a}'
linux

4)输出awk全局变量到文件 5)BEGIN、END、--profile

head -2 /etc/passwd|awk --profile -F: 'BEGIN{OFS="\t\t"}{print"UserName\tUid"}{print $1,$3}END{print".......end........"}'
UserName	Uid
root		0
UserName	Uid
bin		1
.......end........

cat awkprof.out  //会在当前目录下生成awkprof.out文件
	# gawk profile, created Mon Mar 19 10:17:30 2018

	# BEGIN block(s)

	BEGIN {
		OFS = "\t\t"
	}

	# Rule(s)

	{
		print "UserName\tUid"
	}

	{
		print $1, $3
	}

	# END block(s)

	END {
		print ".......end........"
	}

6)/reg/正则匹配

awk 'BEGIN{FS=":"}$1~/root/' /etc/passwd
root:x:0:0:root:/root:/bin/bash

awk -F: 'BEGIN{OFS="=="}/^bin/,/^adm/{print $1,$3,$5}' /etc/passwd
bin==1==bin
daemon==2==daemon
adm==3==adm

awk 'BEGIN{FS=":";OFS=";"}{print $1,$2}' /etc/passwd|head -n5
root;x
bin;x
daemon;x
adm;x
lp;x

awk内置变量

FS      输入字段分隔符,默认是空格或制表符
OFS   输出字段分隔符,默认是空格
RS     输入记录分隔符,默认是换行符\n
ORS   输出记录分隔符,默认是换行符\n
NF     统计当前记录中字段个数
NR     统计记录编号,每处理一行记录,编号就会加1
FNR    统计记录编号,每处理一行记录会加1,处理第二个文件时,编号会重新计数
ARGC   命令行参数数量
ARGIND 当前正在处理的文件索引值
ARGV    命令行参数数组序列数组,下标从0开始,ARGV[0]是awk
ENVIRON  当前系统的环境变量
FILENAME  输出当前处理的文件名
IGNORECASE  忽略大小写
SUBSEP  数组中下标的分隔符,默认为‘\034’
seq -f 'user%02g' 10|sed 'n;n;a\-------'|awk 'BEGIN{RS="-+"}{print $1}'
user01
user04
user07
user10

seq 2 8|awk 'BEGIN{ORS="+"}{print $0}'
2+3+4+5+6+7+8+

awk -F: '$1~/^root/{$5="ROOT";$NF="";print $0}' /etc/passwd
root x 0 0 ROOT /root

awk -F: '{sum+=$3}END{print sum}' /etc/passwd
2605

awk -F: 'NR<=3{print NR":"$0}' /etc/passwd
1:root:x:0:0:root:/root:/bin/bash
2:bin:x:1:1:bin:/bin:/sbin/nologin
3:daemon:x:2:2:daemon:/sbin:/sbin/nologin

echo -e 'a\nb\nc' > a
echo -e 'd\ne\nf' > b
awk '{print NR,FNR,$0}' a b //NR每处理一行就会加1,FNR在处理第二个文件时,行号重新计数
1 1 a
2 2 b
3 3 c
4 1 d
5 2 e
6 3 f

ARGC是命令行参数个数

awk 'BEGIN{print ARGC}' 1 2 3  //awk后有多少个参数,以空格为分隔
4

ARGV是将命令行参数存到数组,元素同ARGC指定,数组下标从0开始

awk 'BEGIN{print ARGV[0],ARGV[1],ARGV[2]}' aa bb
awk aa bb

ARGIND是当前正在处理的文件索引值,第一个文件是1,第二个是2,以此类推

awk '{print ARGIND,$0}' a b
1 a
1 b
1 c
2 d
2 e
2 f

ENVIRON调用系统变量

awk 'BEGIN{print ENVIRON["SHELL"]}'
/bin/bash

[root@localhost prictice]# a=123
[root@localhost prictice]# awk 'BEGIN{print ENVIRON["a"]}'

[root@localhost prictice]# export a
[root@localhost prictice]# awk 'BEGIN{print ENVIRON["a"]}'
123

FILENAME是当前处理文件的文件名

[root@localhost prictice]# awk 'BEGIN{print "文件名\t文件名对应的每行内容"}FNR==NR{print FILENAME"-->"$0}FNR!=NR{print FILENAME"->"$0}' a b
文件名	文件名对应的每行内容
a-->a
a-->b
a-->c
b->d
b->e
b->f

IGNORECASE忽略大小写

[root@localhost ~]# echo "A a b c"|xargs -n1|awk 'BEGIN{IGNORECASE=1}/A/'
A
a

三目运算符

seq 5|awk '{print $0%2==1?"yes":"no"}'
yes
no
yes
no
yes
seq 6|awk '{print n=(n?n","$0:$0)}'
1
1,2
1,2,3
1,2,3,4
1,2,3,4,5
1,2,3,4,5,6
seq 6|awk '{print NR%2?$0:$0"\n我是每隔2行增加的内容"}'
1
2
我是每隔2行增加的内容
3
4
我是每隔2行增加的内容
5
6
我是每隔2行增加的内容
seq 6|awk '{printf NR%2!=0?$0" ":$0" \n"}' 或 seq 6|awk 'ORS=NR%2?" ":"\n"' 或 seq 6 |awk '{if(NR%2)ORS=" ";else ORS="\n";print}'
1 2 
3 4 
5 6

if语句

seq 3|awk '{if($0==3){print $0}}'
3
seq 3|awk '{if($0==3)print "yes";else print "no"}'
no
no
yes
head -2 /etc/passwd|awk -F: '{if($1=="root"){print $0} else if($1=="ftp"){print $0} else print "no"}'
root:x:0:0:root:/root:/bin/bash
no

while语句

[root@localhost ~]# cat file
1 2 3
4 5 6
7 8 9
[root@localhost ~]# awk '{i=1;while(i<=NF){printf $i" ";i++};print ""}' file
1 2 3 
4 5 6 
7 8 9

for语句

awk '{for(i=1;i<=NF;i++){printf $i" "};{print ""}}' file
awk '{for(i=NF;i>=1;i--){printf $i" "};{print ""}}' file
3 2 1 
6 5 4 
9 8 7
echo "192.168.221.10 192.168.221.20 192.168.221.30"|awk '{for(i=1;i<=NF;i++)printf "\047"$i"\047";print ""}'
'192.168.221.10''192.168.221.20''192.168.221.30'

break、continue

awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){break};print i}}'
awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){continue};print i}}'

数组

tail -3 /etc/passwd|awk -F: '{a[NR]=$1}END{print a[1],a[2],a[3]}'  //数组下标从1开始
postfix sshd chrony

tail /etc/passwd|awk -F: '{a[NR]=$1}END{for(i=1;i<=NR;i++)print i":::"a[i]}'
1:::operator
2:::games
3:::ftp
4:::nobody
5:::systemd-network
6:::dbus
7:::polkitd
8:::postfix
9:::sshd
10:::chrony
awk -F: '{a[NR]=$1}END{for(i in a)print i"---"a[i]}' /etc/passwd
17---postfix
4---adm
18---sshd
5---lp
19---chrony
6---sync
7---shutdown
8---halt
9---mail
10---operator
11---games
12---ftp
13---nobody
14---systemd-network
1---root
15---dbus
2---bin
16---polkitd
3---daemon
tail -5 /etc/passwd|awk -F: '{a[x++]=$1}END{for(i=0;i<=x-1;i++)print i"=="a[i]}'
0==dbus
1==polkitd
2==postfix
3==sshd
4==chrony
awk -F: '{a[$NF]++}END{for(v in a)print a[v]"\t"v}' /etc/passwd
1	/bin/sync
1	/bin/bash
15	/sbin/nologin
1	/sbin/halt
1	/sbin/shutdown

awk -F: '{a[$NF]++}END{for(i in a) if(a[i]>2)print a[i]"\t"i}' /etc/passwd
15	/sbin/nologin
netstat -tlnp|awk '/^tcp/{a[$6]++}END{for(i in a)print a[i]"\t"i}'
4	LISTEN
awk -F: '{if(a[$NF]++)print $1}' /etc/passwd  //打印shell重复对应的用户名(但不打印重复的起始的那个)
awk -F: '{if(!a[$NF]++)print $1}' /etc/passwd  //与上面相反

统计每个相同字段对应的另一个字段的总和

tail /etc/services |awk -F'[ /]+'  '{a[$1]+=$2}END{for(v in a)print v,a[v]}'
com-bardac-dw 97112
3gpp-cbsp 48049
iqobject 97238
matahari 49000
isnetserv 96256
blp5 96258

多维数组

awk 'BEGIN{a["x","y"]=123;for(v in a)print v,a[v]}'
xy 123
awk 'BEGIN{SUBSEP=":";a["x","y"]=123;for(v in a)print v,a[v]}'
x:y 123
cat ip.txt
A 192.168.1.1 
B 192.168.1.2 
B 192.168.1.2 
C 192.168.1.1 
C 192.168.1.1 
D 192.168.1.4
//根据指定的字段统计出现次数
awk 'BEGIN{SUBSEP="-"}{a[$1,$2]++}END{for(v in a)print a[v],v}' ip.txt
1 D-192.168.1.4
1 A-192.168.1.1
2 C-192.168.1.1
2 B-192.168.1.2

内置函数

int(expr)         // 截断为整数
sqrt(expr)        //平方根
rand()               //返回一个随机数N,0和1范围,0<N<1
srand([expr])    //使用expr生成随机数,如果不指定,默认使用当前时间为种子,如果前面有种子则使用生成随机数
asort(a,b)   //对数组a的值进行排序,把排序后的值存到新的数组b中,新排序的数组下标从1开始
asorti(a,b)  //对数组a的下标进行排序,同上
sub(r,s[, t])   //对输入的记录用s替换r,t可选针对某字段,但只替换第一字符串
gsub(r,s [, t])  //对输入的记录用s替换r,t可选针对某字段替换,替换所有字符串
index(s,t)    //返回s中字符串t的索引位置,0为不存在
length([s])  //返回s的长度
match(s,r [, a])   //测试字符串s是否包含匹配r的字符串
split(s,a [,r [, seps]])  //根据分隔符seps将s分成数组
substr(s,i [, n])  //截取字符串s从i开始到长度n,如果n没有指定则是剩余部分
tolower(str)  //str中的所有大写转换成小写
toupper(str)   //str中的所有小写转换成大写
systime()    //当前时间戳
strftime([format [, timestamp [, utc-flag]]])  //格式化输出时间,将时间戳转为字符串

示例

int()

echo "11ab11 012ab ab123"|xargs -n1|awk '{print int($0)}'
11
12
0

sqrt()

awk 'BEGIN{print sqrt(81)}'
9

rand()、srand()

awk 'BEGIN{print rand()}'  //不是每次运行就是一个随机数,会一直保持一个不变
awk 'BEGIN{srand();print rand()}'  //会生成一个变化的随机数
awk 'BEGIN{srand();print int(rand()*10)}'  //[0-10)之间的整数

asort()、asorti()

//asort(a,b)将数组a的每个元素的值进行排序放到数组b中,b数组的下标从1开始,返回值是数组b元素的个数
seq -f "str%.g" 5|awk '{a[x++]=$0}END{s=asort(a,b);for(i=1;i<=s;i++)print b[i],i}'
str1 1
str2 2
str3 3
str4 4
str5 5
//asorti(a,b)将数组a的下标进行排序放到数组b中,b数组的下标从1开始,返回值是数组b元素的个数
seq -f 'str%.g' 5|awk '{a[x++]=$0}END{s=asorti(a,b);for(i=1;i<=s;i++)print b[i],i}'
0 1
1 2
2 3
3 4
4 5

sub() 、gsub()

	//匹配“blp5”的行中含有“tcp”的替换成“icmp”
tail /etc/services |awk '/blp5/{sub(/tcp/,"icmp");print $0}'
blp5            48129/icmp               # Bloomberg locator
blp5            48129/udp               # Bloomberg locator
	//匹配“blp5”的行中含有“c"替换成“9”
tail /etc/services |awk '/blp5/{gsub(/c/,"9");print $0}'
blp5            48129/t9p               # Bloomberg lo9ator
blp5            48129/udp               # Bloomberg lo9ator
//针对第二个字段将2的值替换成7
echo "1 2 2 3 4 5"|awk 'gsub(2,7,$2){print $0}'
//将数字替换成0
echo "1 2 3 a b c"|awk 'gsub(/[[:digit:]]/,'0'){print $0}'
0 0 0 a b c
//在指定行前后加一行
seq 5|awk 'NR==2{sub('/.*/',"txt\n&")}{print}'
seq 5|awk 'NR==2{sub('/.*/',"&\ntxt")}{print}'

index()

tail -n5 /etc/services |awk '{print index($2,"tcp")}'  //“tcp”在$2中的位置
7
0
7
0
7

length()

//统计字段的长度
head -n3 /etc/passwd|awk -F: '{print length($1)}'
4
3
6
//统计数组的长度
awk -F: '{user[x++]=$1}END{print length(user)}' /etc/passwd
19

split(string,array,delimiter)

echo "http://www.baidu.com/admin/index.php"|awk '{split($0,a,"/|//");for(k in a)print a[k],k}'
index.php 4
http: 1
www.baidu.com 2
admin 3

substr(string,index,length)

echo "http://www.baidu.com/admin/index.php"|awk '{print substr($0,1,7)}'
http://

toupper(string)

echo "www.baidu.com"|awk '{print toupper($0)}'
WWW.BAIDU.COM

tolower(string)

echo "WWW.badu.com"|awk '{print tolower(substr($0,1,3))}'
www

返回当前时间戳

awk 'BEGIN{print systime()}'
1521527470

将时间戳转为日期和时间

echo "1521527470"|awk '{print strftime("%F %T",$0)}'
2018-03-20 14:31:10

i/o语句

getline   //设置$0来自下一个输入记录
getline var   //设置var来自下一个记录
command | getline [var]  //运行命令管道输出到$0或var
next    //停止当前处理的输入记录
print    //打印当前记录
printf fmt,expr-list   //格式输出
printf fmt,expr-list > file   //格式输出和写到文件
system(cmd-line) //执行命令和返回状态
print ...  >> file   //追加输出到文件
print ... | command  //打印输出作为命令输入

getline

seq 5 |awk '/3/{print;getline;sub(".*","&*");print}'
3
4*

getline var

cat name.txt
zhangsan
lisi
wangwu
zhaoliu

cat age.txt
26
18
30
36

awk '{getline var < "age.txt";print $0,var}' name.txt
zhangsan 26
lisi 18
wangwu 30
zhaoliu 36
[root@localhost ~]# cat name.txt 
zhangsan 12
lisi 12
wangwu 12
zhaoliu 12
[root@localhost ~]# cat age.txt 
26
18
30
36

修改name.txt将第二字段全部成age.txt中的数据
awk '{getline line < "age.txt";gsub($2,line,$2);print}' name.txt
zhangsan 26
lisi 18
wangwu 30
zhaoliu 36
[root@localhost ~]# awk 'BEGIN{"seq 5" |getline var;print var}'
1
[root@localhost ~]# awk 'BEGIN{while("seq 5"|getline)print}'
1
2
3
4
5

next

seq 5|awk '{if($0==3){next}else{print}}'   //1,2,4,5
seq 5|awk 'NR==1{next}{print $0}'  //2,3,4,5
seq 5|awk 'NR!=1{print}'  //2,3,4,5
cat a
hello
1 a
2 b
3 c

awk 'NR==1{s=$0}NF!=1{print s,$0}' a  或 awk 'NR==1{s=$0}NF!=1{print s,$0}' a
hello 1 a
hello 2 b
hello 3 c

system()

awk 'BEGIN{if(system("grep -wE ^root /etc/passwd &> /dev/null")==0)print "yes";else print "no"}'
yes

将结果写入到文件中

awk -F: '{a[$NF]++}END{for(i in a)print a[i],i > "state.txt"}' /etc/passwd
cat state.txt
1 /bin/sync
1 /bin/bash
15 /sbin/nologin
1 /sbin/halt
1 /sbin/shutdown

管道连接shell命令

awk -F: 'NR>2&&NR<18{print $0 | "grep ftp"}' /etc/passwd
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin

printf语句

格式化输出,默认打印字符串不换行

格式:printf [format] arguments

%s     //一个字符串
%d,%i   //一个小数
%f   //一个浮点数
%.ns  //输出字符串,n是输出几个字符
%ni    //输出整数,n是输出几个数字
%m.nf  //输出浮点数,m是输出整数位数,n是输出的小数位数
%x  //不带正负号的十六进制,使用a至f表示10到15
%X  //不带正负号的十六进制,使用A至F表示10至15
%%  //输出单个%
%-5s   //左对齐,对参数每个字段左对齐,宽度为5
%-4.2f 左对齐,宽度为4,保留两位小数
%5s 右对齐,不加横线表示右对齐
vim test.awk
BEGIN{
print "+--------------------+--------------------+";
printf "|%-20s|%-20s|\n","Name","Number";
print "+--------------------+--------------------+";
}
awk -f test.awk
+--------------------+--------------------+
|Name                |Number              |
+--------------------+--------------------+

自定义函数

awk 'function myfunc(a,b){return a+b}BEGIN{print myfunc(1,2)}'
3

nginx日志格式

'$remote_addr $http_x_forwarded_for [$time_local]' ' $host "$request_uri" $status' ' "$http_referer" "$http_user_agent"'

//统计ip访问次数
awk '{a[$1]++}END{for(i in a)print i,a[i]}' /tmp/access.log
127.0.0.1 5
192.168.221.1 125
192.168.221.10 16
//统计访问大于100次的ip
awk '{a[$1]++}END{for(i in a)if(a[i]>100)print i,a[i]}' /tmp/access.log
192.168.221.1 125
//统计访问ip次数倒序排列
awk '{a[$1]++}END{for(i in a)print i,a[i]|"sort -k2 -nr"}' /tmp/access.log
192.168.221.1 125
192.168.221.10 16
127.0.0.1 5
//统计上一分钟访问量
date=$(date -d '-1 min' +%d/%b/%Y:%H:%M)
awk -v date=$date '$3~date{c++}END{print c}' /tmp/access.log
//统计访问最多的一个页面
awk '{a[$6]++}END{for(v in a)print v,a[v]|"sort -k2 -nr|head -1"}' /tmp/access.log
//统计每个ip访问状态码为404的次数
awk '{if($7~/404/)a[$1" "$7]++}END{for(i in a)print i,a[i]}' /tmp/access.log
//统计每个ip访问状态码数量
awk '{a[$1" "$7]++}END{for(v in a)print v,a[v]}' /tmp/access.log