awk简介
awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。awk的功能是什么?与sed和grep很相似,awk是一种样式扫描与处理工具。但其功能却大大强于sed和grep。awk提供了极其强大的 功能:它几乎可以完成grep和sed所能完成的全部工作,同时,它还可以可以进行样式装入、流控制、数学运算符、进程控制语句甚至于内置的变量和函数。 它具备了一个完整的语言所应具有的几乎所有精美特性。实际上,awk的确拥有自己的语言:awk程序设计语言,awk的三位创建者已将它正式定义为:样式 扫描和处理语言。
一、awk命令形式:
awk [-F|-f|-v] 'BEGIN{} / /{command1; command2} END{}' file
1)[-F|-f|-v] 大参数,-F指定分隔符,-f调用脚本,-v定义变量 var=value
2)'' 引用代码块
3)BEGIN 初始化代码块,在对每一行进行处理之前,初始化代码,主要是引用全局变量,设置FS分隔符
4)// 匹配代码块,可以是字符串或正则表达式
5){} 命令代码块,包含一条或多条命令
6); 多条命令使用分号分隔
7)END 结尾代码块,在对每一行进行处理之后再执行的代码块,主要是进行最终计算或输出结尾摘要信息
以上命令格式,可以是完整的,也可以只是部分。
二、awk的调用方式
(1)awk命令行
命令格式:awk [-F field-separator] 'commands' input-file(s)
[-F域分隔符]是可选项,字段分隔符。commands 是真正awk命令,它可以分为两部分:'pattern {action}',即用正则查找'/re/{action}',当pattern为真,则执行{action}
1)其中pattern参数可以是egrep正则表达式中的任何一个,使用语法/re/再加上一些样式匹配技巧构成。与sed类似,你也可以使 用","分开两样式以选择某个范围。
2)action参数总是被大括号包围,它由一系统awk语句组成,可以有多个语句,各语句之间用";"分隔。
3)input-file(s) 是待处理的文件。
在awk中,文件的每一行中,由域分隔符分开的每一项称为一个域。通常,在不指名-F域分隔符的情况下,默认的域分隔符是空格。
awk工作流程:读入有'\n'换行符分割的一条记录,然后将记录按指定的域分隔符划分域,填充域,$0则表示所有域,$1表示第一个域,$n表示第n个域。默认域分隔符是"空白键" 或 "[tab]键"。
例1:
[root@controller1 ~]# ll /root
-rw-------. 1 root root 268 Jun 7 17:28 admin-openrc
-rw-------. 1 root root 3068 May 4 01:47 anaconda-ks.cfg
-rw-r--r-- 1 root root 13267968 Feb 10 2017 cirros-0.3.5-x86_64-disk.img
-rw-r--r--. 1 root root 3088 May 4 01:47 cobbler.ks
-rw-r--r-- 1 root root 3088 Jun 9 11:36 cobbler.ks_2018-06-09
-rw-r--r-- 1 root root 3088 Jun 9 11:36 cobbler.ks_2018-06-09+11:36:52
-rw-r--r-- 1 root root 3088 Jun 9 11:37 cobbler.ks_2018-06-0911:37:11
-rw-r--r-- 1 root root 3088 Jun 9 11:37 cobbler.ks_2018-06-09_11:37:41
-rw-------. 1 root root 262 Jun 7 17:28 demo-openrc
[root@controller1 ~]# ll /root|awk '/cobb/{print $9}'
cobbler.ks
cobbler.ks_2018-06-09
cobbler.ks_2018-06-09+11:36:52
cobbler.ks_2018-06-0911:37:11
cobbler.ks_2018-06-09_11:37:41
先通过正则,查找所有cobb的行,并将其第9列输出来。
[root@controller1 ~]# ll /root|awk '{print $9}'|awk '/^c/'
cirros-0.3.5-x86_64-disk.img
cobbler.ks
cobbler.ks_2018-06-09
cobbler.ks_2018-06-09+11:36:52
cobbler.ks_2018-06-0911:37:11
cobbler.ks_2018-06-09_11:37:41
先输出所有行的第9列,(此时只有一列数据,就是原来的第9列)再通过正则,输出以c开头的行。
(2).将所有的awk命令插入一个单独文件,然后调用:
调用格式:awk -f awk-script-file input-file(s)
其中,-f选项加载awk-script-file中的awk脚本,input-file(s)跟上面的是一样的。
使用-f选项调用awk程序。awk允许将一段awk程序写入一个文本文件,然后在awk命令行中用-f选项调用并执行这段程序。
(3).利用命令解释器调用awk程序:
将所有的awk命令插入一个文件,并使awk程序可执行,然后awk命令解释器作为脚本的首行,一遍通过键入脚本名称来调用。
相当于shell脚本首行的:#!/bin/sh 将其换成:#!/bin/awk 并赋予这个文本文件以执行的权限。
三、awk内置变量
awk有许多内置变量用来设置环境信息,这些变量可以被改变,下面给出了最常用的一些变量。
ARGC 命令行参数个数
ARGV 命令行参数排列
ENVIRON 支持队列中系统环境变量的使用
FILENAME awk浏览的文件名
FNR 浏览文件的记录数
FS 设置输入域分隔符,等价于命令行 -F选项
NF 浏览记录的域的个数
NR 已读的记录数
OFS 输出域分隔符
ORS 输出记录分隔符
RS 控制记录分隔符
\t 制表符
\n 换行符
-F'[:#/]' 同时定义三个分隔符
~ 匹配,与==相比不是精确比较
!~ 不匹配,不精确比较
== 等于,必须全部相等,精确比较
!= 不等于,精确比较
&& 逻辑与
|| 逻辑或
+ 匹配时表示1个或1个以上
/[0-9][0-9]+/ 两个或两个以上数字
/[0-9][0-9]*/ 一个或一个以上数字
$0变量是指整条记录。$1表示当前行的第一个域,$2表示当前行的第二个域,......以此类推。
例1:匹配有mysql或mail的行,并输出该行
[root@controller1 ~]# awk '/mysql|mail/{print}' /etc/passwd
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
mysql:x:27:27:MySQL Server:/var/lib/mysql:/sbin/nologin
与该命令相同:awk -F: '$1~/mail|mysql/{print $0}' /etc/passwd
例2:以":"分割,第1列匹配有mail字串的,输出该行
[root@controller1 ~]# awk -F: '$1~/mail/{print $0}' /etc/passwd
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
与该命令相同:awk -F: '{if($1~/mail/) print $0}' /etc/passwd
四、awk内置函数
gsub(reg,string,target) 每次常规表达式reg匹配时替换target中的string
index(search,string) 返回string中search串的位置
length(string) 求串string中的字符个数
match(string,reg) 返回常规表达式reg匹配的string中的位置
printf(format,variable) 格式化输出,按format提供的格式输出变量variable。
split(string,store,delim) 根据分界符delim,分解string为store的数组元素
sprintf(format,variable) 返回一个包含基于format的格式化数据,variables是要放到串中的数据
strftime(format,timestamp) 返回一个基于format的日期或者时间串,timestmp是systime()函数返回的时间
sub(reg,string,target) 第一次当常规表达式reg匹配,替换target串中的字符串
substr(string,position,len) 返回一个以position开始len个字符的子串
totower(string) 返回string中对应的小写字符
toupper(string) 返回string中对应的大写字符
atan(x,y) x的余切(弧度)
cos(x) x的余弦(弧度)
exp(x) e的x幂
int(x) x的整数部分
log(x) x的自然对数值
rand() 0-1之间的随机数
sin(x) x的正弦(弧度)
sqrt(x) x的平方根
srand(x) 初始化随机数发生器。如果忽略x,则使用system()
system() 返回自1970年1月1日以来经过的时间(按秒计算)
print 函数的参数可以是变量、数值或者字符串。字符串必须用双引号引用,参数用逗号分隔。
printf 基本相似,可以格式化字符串
例1:输出print和printf
[root@controller1 ~]# awk -F ':' '{printf("filename:%10s,linenumber:%s,columns:%s,linecontent:%s\n",FILENAME,NR,NF,$0)}' /etc/passwd
filename:/etc/passwd,linenumber:1,columns:7,linecontent:root:x:0:0:root:/root:/bin/bash
filename:/etc/passwd,linenumber:2,columns:7,linecontent:bin:x:1:1:bin:/bin:/sbin/nologin
filename:/etc/passwd,linenumber:3,columns:7,linecontent:daemon:x:2:2:daemon:/sbin:/sbin/nologin
filename:/etc/passwd,linenumber:4,columns:7,linecontent:adm:x:3:4:adm:/var/adm:/sbin/nologin
filename:/etc/passwd,linenumber:5,columns:7,linecontent:lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
filename:/etc/passwd,linenumber:6,columns:7,linecontent:sync:x:5:0:sync:/sbin:/bin/sync
filename:/etc/passwd,linenumber:7,columns:7,linecontent:shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
五、awk的模式和操作
awk脚本是由模式和操作组成的:
格式:pattern {action} 如$ awk '/root/' test,或$ awk '$3 < 100' test。
两者是可选的,如果没有模式,则action应用到全部记录,如果没有action,则输出匹配全部记录。
默认情况下,每一个输入行都是一条记录,但用户可通过RS变量指定不同的分隔符进行分隔。
(1).模式
模式可以是以下任意一个:
1)正则表达式:使用通配符的扩展集。
2)关系表达式:可以用下面运算符表中的关系运算符进行操作,可以是字符(3)串或数字的比较,如$2>%1选择第二个字段比第一个字段长的行。
4)模式匹配表达式:用运算符~(匹配)和~!(不匹配)。
5)模式,模式:指定一个行的范围。该语法不能包括BEGIN和END模式。
6)BEGIN:让用户指定在第一条输入记录被处理之前所发生的动作,通常可在这里设置全局变量。
7)END:让用户在最后一条输入记录被读取之后发生的动作。
(2).操作
操作由一个或多个命令、函数、表达式组成,之间由换行符或分号;隔开,并位于大括号{}内。主要有四部份:
1)变量或数组赋值
2)输出命令
3)内置函数
4)控制流命令
六、awk的运算符
= += -= *= /= %= ^= **= 赋值
?: C条件表达式
|| 逻辑或
&& 逻辑与
~ ~! 匹配正则表达式和不匹配正则表达式
< <= > >= != == 关系运算符
空格 连接
+ - 加,减
* / & 乘,除与求余
+ - ! 一元加,减和逻辑非
^ *** 求幂
++ -- 增加或减少,作为前缀或后缀
$ 字段引用
in 数组成员
七、awk的记录和域(即列)
(1)关于记录
awk把每一个以换行符结束的行称为一个记录。
1)记录分隔符:默认的输入和输出的分隔符都是回车,保存在内建变量ORS和RS中。
2)$0变量:它指的是整条记录。如$ awk '{print $0}' test将输出test文件中的所有记录。
3)变量NR:一个计数器,每处理完一条记录,NR的值就增加1。
[root@controller1 ~]# awk '{print NR,$0}' admin-openrc
1 export OS_PROJECT_DOMAIN_NAME=Default
2 export OS_USER_DOMAIN_NAME=Default
3 export OS_PROJECT_NAME=admin
4 export OS_USERNAME=admin
5 export OS_PASSWORD=ADMIN_PASS
6 export OS_AUTH_URL=http://172.16.100.70:5000/v3
(2)域即列
1)记录中每个单词称做“域”,默认情况下以空格或tab分隔。awk可跟踪域的个数,并在内建变量NF中保存该值。
[root@controller1 ~]# awk -F ':' '{printf("%10s %20s\n",$1,$7)}' /etc/passwd
root /bin/bash
bin /sbin/nologin
daemon /sbin/nologin
adm /sbin/nologin
lp /sbin/nologin
sync /bin/sync
[root@controller1 ~]# awk -F ':' '{printf("%-20s %-20s\n",$1,$7)}' /etc/passwd
root /bin/bash
bin /sbin/nologin
daemon /sbin/nologin
adm /sbin/nologin
lp /sbin/nologin
sync /bin/sync
输出passwd文件中,以“:”为分割符,将第1,7列的数据,格式化输出,注意对齐方式。
(3)域分隔符
1)内建变量FS保存输入域分隔符的值,默认是空格或tab。
可以通过-F命令行选项修改FS的值。如:awk -F : '{print $1,$5}' test.txt 将打印以冒号为分隔符的第一,第五列的内容。
2)可以同时使用多个域分隔符,这时应该把分隔符写成放到方括号中。
如$awk -F '[:/t]' '{print $1,$3}' test,表示以空格、冒号和tab作为分隔符。
输出域的分隔符默认是一个空格,保存在OFS中。如$ awk -F: '{print $1,$5}' test,$1和$5间的逗号就是OFS的值。
八、awk编程
(1)条件表达式 == != > >=
awk -F":" '$1=="mysql"{print $0}' /etc/passwd
awk -F":" '{if($1=="mysql") print $0}' /etc/passwd //与上面相同
awk -F":" '$1!="mysql"{print $0}' /etc/passwd //不等于
awk -F":" '$3>100{print $0}' /etc/passwd //大于
awk -F":" '$3>=100{print $0}' /etc/passwd //大于等于
awk -F":" '$3<1{print $3}' /etc/passwd //小于
awk -F":" '$3<=1{print $3}' /etc/passwd //小于等于
(2)逻辑运算符 && ||
awk -F: '$1~/mail/ && $3>3 {print }' /etc/passwd //逻辑与,$1匹配mail,并且$3>8
awk -F: '{if($1~/mail/ && $3>3) print }' /etc/passwd
awk -F: '$1~/mail/ || $3>100 {print }' /etc/passwd //逻辑或
awk -F: '{if($1~/mail/ || $3>100) print }' /etc/passwd
(3)数值运算
awk -F: '$3 > 100' /etc/passwd
awk -F: '$3 > 100 || $3 < 5' /etc/passwd
awk -F: '$3+$4 > 200' /etc/passwd
awk -F: '/mysql|mail/{print $3+10}' /etc/passwd //第三个字段加10打印
awk -F: '/mysql/{print $3-$4}' /etc/passwd //减法
awk -F: '/mysql/{print $3*$4}' /etc/passwd //求乘积
awk '/MemFree/{print $2/1024}' /proc/meminfo //除法
awk '/MemFree/{print int($2/1024)}' /proc/meminfo //取整
(4)输出分隔符OFS
[root@controller1 ~]# netstat |awk '$6 ~ /WAIT/ || NR==1 {print NR,$4,$5,$6}' OFS="\t"
75 controller1:59538 controller:openstack-id CLOSE_WAIT
105 controller1:59534 controller:openstack-id CLOSE_WAIT
333 controller1:epmd controller1:58014 TIME_WAIT
338 controller1:51142 controller1:mysql TIME_WAIT
379 controller1:8778 compute3:49788 FIN_WAIT2
//输出字段6匹配WAIT的行,其中输出每行行号,字段4,5,6,并使用制表符分割字段,
(5)输出处理结果到文件
1)使用重定向进行输出
[root@controller1 ~]# netstat |awk '$6 ~ /WAIT/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" >netstat.txt
(6)格式化输出
netstat -anp|awk '{printf "%-8s %-8s %-10s\n",$1,$2,$3}'
printf表示格式输出
%格式化输出分隔符
左对齐:%-8长度为8个字符
右对齐:%8长度为8个字符
s表示字符串类型
[root@controller1 ~]# netstat -anp|awk '$6=="ESTABLISHED" || NR==1 {printf "%-5s %-6s %-25s %-25s \n",NR,$1,$4,$5}'
1 Active (servers and
16 tcp 172.16.100.70:11211 172.16.100.70:47038
17 tcp 172.16.100.70:41336 172.16.100.70:5672
18 tcp 172.16.100.70:49814 172.16.100.70:11211
19 tcp 172.16.100.70:47310 172.16.100.70:11211
20 tcp 172.16.100.70:41354 172.16.100.70:5672
21 tcp 172.16.100.70:51328 172.16.100.70:3306
22 tcp 172.16.100.70:51232 172.16.100.70:3306
23 tcp 172.16.100.70:43500 172.16.100.70:5672
24 tcp 172.16.100.70:51202 172.16.100.70:3306
匹配第6列为ESTABLISHED,并且格式化输出行号,第1,4,5列的数据
(7)IF语句
[root@controller1 ~]# awk -F: 'BEGIN{A=0;B=0} {if($3>10) {A++; print A,"large"} else {B++; print B,"small"}} END{print "LARGE_total:",A,"\t\t","SMALL_total:",B}' /etc/passwd
1 small
2 small
...
8 small
9 small
1 large
2 large
...
26 large
27 large
LARGE_total: 27 SMALL_total: 9
语句模块,三部分都用{}括起来,内部用分号,逗号分开,中间过程部分,内部还要用大括号{}:
1)开始模块:BEGIN{A=0;B=0}
2)中间过程模块:{if($3>10) {A++; print A,"large"} else {B++; print B,"small"}}
3)结束模块:END{print "LARGE_total:",A,"\t\t","SMALL_total:",B}
在一条命令中,不一定要这三部分都有,只有中间过程模块就行。
(8)while语句
[root@controller1 ~]# awk -F ':' 'BEGIN {count=0;} {name[count] = $1;count++;} END{for (i = 0; i < NR; i++) print i, name[i]}' /etc/passwd
0 root
1 bin
2 daemon
3 adm
4 lp
...
15 polkitd
用一个数组暂存,取到的数据
[root@controller1 ~]# awk -F: 'BEGIN{i=1} {while(i<NF) {print i,$i;i++}}' /etc/passwd
1 root
2 x
3 0
4 0
5 root
6 /root
将第1行记录中的,每一列都分别输出来。注意在语句中:大括号,分号,逗号的使用。
九、应用实例
(1)统计当前目录下所有文件的大小,使用KB作为单位,int是取整的意思
[root@controller1 ~]# ls -l|awk 'BEGIN{sum=0} !/^d/{sum+=$5} END{print "total size is:",int(sum/1024),"KB"}'
total size is: 298195 KB
(2)统计netstat -anp 状态为LISTEN和CONNECT的连接数量分别是多少
[root@controller1 ~]# netstat -anp|awk '$6~/LISTEN|CONNECTED/{sum[$6]++} END{for (i in sum) printf "%-10s %-6s %-3s \n", i," ",sum[i]}'
LISTEN 22
CONNECTED 124
(3)统计/etc目录下不同用户的普通文件的总数是多少?
[root@controller1 etc]# ls -l|awk 'NR!=1 && !/^d/{sum[$3]++} END{for (i in sum) printf "%-6s %-5s %-3s \n",i," ",sum[i]}'
tss 1
root 122
(4)统计/etc目录下不同用户的普通文件的大小总size是多少?
ls -l|awk 'NR!=1 && !/^d/{sum[$3]+=$5} END{for (i in sum) printf "%-6s %-5s %-3s %-2s \n",i," ",sum[i]/1024/1024,"MB"}'
十、awk与shell之间变量传递
(1)awk引用shell变量
1)"'$var'"
[root@controller1 etc]# var="xiaoming"
[root@controller1 etc]# awk 'BEGIN{print "'$var'"}'
xiaoming
这种写法外层双引号,内层单引号,$再加变量名。这种写法其实际是双括号变为单括号的常量,传递给了awk.
如果var中含空格,为了shell不把空格作为分格符,便应该如下使用:
[root@controller1 etc]# var="i am xiao ming"
[root@controller1 etc]# awk 'BEGIN{print "'"$var"'"}'
i am xiao ming
2)export变量,使用ENVIRON["var"]形式,获取环境变量的值(变量多建议使用)
定义一个环境变量
[root@controller1 etc]# var="i am xiao ming"
[root@controller1 etc]# export var
查看一下环境变量
[root@controller1 etc]# env
...
COBBLER_SERVER=172.16.70.110
MAIL=/var/spool/mail/root
var=i am xiao ming
HISTCONTROL=ignoredups
HOME=/root
LOGNAME=root
...
用awk取一个环境变量
[root@controller1 etc]# awk 'BEGIN{print ENVIRON["var"]}'
i am xiao ming
[root@controller1 etc]# awk 'BEGIN{print ENVIRON["MAIL"]}'
/var/spool/mail/root
3)可以使用awk的-v选项(变量不多,建议使用)
[root@controller1 etc]# awk -v str="$var" 'BEGIN{print str}'
i am xiao ming
[root@controller1 etc]# awk -v str="$HOME" 'BEGIN{print str}'
/root
这样便把系统变量var和HOME传递给了awk变量str.
(2)shell中使用awk变量,利用eval和awk的print结合,获取awk中的变量值
[root@controller1 ~]# eval $(awk 'BEGIN{print " var1='xiaoming';var2='xiaohong' "}')
[root@controller1 ~]# echo $var1
xiaoming
[root@controller1 ~]# echo $var2
xiaohong
取得/etc/passwd最后一条记录值
[root@controller1 ~]# eval $(awk -F: '{printf("var1=%s; var2=%s; var3=%s;",$1,$2,$3)}' /etc/passwd)
[root@controller1 ~]# echo "$var1 $var2 $var3"
neutron x 993
十一、awk性能比较
(1)定义一个数列:chars=`seq -s " " 100`
[root@controller1 etc]# chars=`seq -s " " 100`
[root@controller1 etc]# echo $chars
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
(2)用sell命令统计这个数列的字符长度10000次:${#chars}
[root@controller1 etc]# time (for i in $(seq 10000);do count=${#chars};done;)
real 0m0.273s
user 0m0.269s
sys 0m0.004s
(3)用awk的length函数统计这个数列的字符长度10000次:awk 'BEGIN{str="abc";print length(str)}'
[root@controller1 ~]# awk -v str="$chars" 'BEGIN{print length(str)}'
291
[root@controller1 ~]# time (awk -v str="$chars" 'BEGIN{for(i=0;i<=10000;i++){count=length(str)}}')
real 0m0.006s
user 0m0.004s
sys 0m0.003s
可以发现awk进行字符统计,速度是非常快的
(4)其它方法都比较慢:expr length "${chars}"或 echo $chars |wc -m
[root@controller1 ~]# time (for i in $(seq 10000);do count=$(expr length "${chars}");done;)
real 0m22.564s
user 0m5.785s
sys 0m17.594s
[root@controller1 ~]# time (for i in $(seq 10000);do count=`echo $chars |wc -m `;done;)
real 0m29.506s
user 0m10.109s
sys 0m28.918s
参考:https://www.cnblogs.com/chengmo/archive/2013/01/17/2865479.html