- awk介绍
- awk变量
- printf命令:实现格式化输出
- 操作符
- awk pattern
- awk action
- awk数组
- awk函数
- awk脚本
一、awk介绍
- 功能:
报告生成器,模式化文本输出
同时也是一门语言:模式扫描和处理语言
(一)awk的基本语法概念
- 基本格式:
awk [options] 'program' file…
awk [options] -f program_file file…
-
program通常由:BEGIN语句块、能够使用模式匹配的通用语句块、END语句块,共3部分组成,通常放置于单引号或双引号中
- program格式:BEGIN{ action;… } pattern{ action;… } END{ action;… }
- pattern:决定动作语句何时触发及触发事件
- action statements:对数据进行处理,放在{}内指明
选项:
-F:指明输入时用到的字段分隔符,默认以空格作为分隔符
-v var=value:自定义变量-
分割符、域和记录
- 文件的每一行称为记录,每一列称为字段(域)
- awk执行时,由分隔符分隔的字段(域)称为域标识,标记为$1,$2..$n,$0为所有域。
- 注意:此处的$与shell中变量$符号含义不同
- 省略action,则默认执行print $0 的操作
(二)awk的工作原理
-
第一步:执行BEGIN{action;… }语句块中的语句
- BEGIN语句块在awk开始从输入流中读取行之前被执行,这是一个可选的语句块,比如变量初始化、打印输出表格的表头等语句通常可以写在BEGIN语句块中
-
第二步:从文件或标准输入(stdin)读取一行,然后执行pattern{ action;… }语句块,它逐行扫描文件,从第一行到最后一行重复这个过程,直到文件全部被读取完毕。
- pattern语句块中的通用命令是最重要的部分,也是可选的。如果没有提供pattern语句块,则默认执行{ print },即打印每一个读取到的行,awk读取的每一行都会执行该语句块
-
第三步:当读至输入流末尾时,执行END{action;…}语句块
- END语句块在awk从输入流中读取完所有的行之后即被执行,比如打印所有行的分析结果这类信息汇总都是在END语句块中完成,它也是一个可选语句块
(三)print语句
-
语法:print item1, item2, ...
- 逗号分隔符
- 输出的各item可以字符串,也可以是数值;当前记录的字段、变量或awk的表达式
- 省略item,相当于print $0
示例1-1:
awk '{print "hello, awk"}' //等待输入,每输入一条打印一次"hello, awk"
awk '{print}' /etc/passwd //逐条打印/etc/passwd文件
awk '{print "wang"}' /etc/passwd //每读入/etc/passwd文件一行,打印一次"wang"
awk -F: '{print $1}' /etc/passwd //打印/etc/passwd文件以":"作为分隔符的第1列
awk -F: '{print $0}' /etc/passwd //逐条打印/etc/passwd文件
awk -F: '{print $1,$3}' /etc/passwd //打印以":"作为分隔符的第1列和第3列,以空格作为分隔符输出
awk -F: '{print $1"\t"$3}' /etc/passwd //打印以":"作为分隔符的第1列和第3列,以制表符作为分隔符输出
tail -3 /etc/fstab | awk '{print $2,$4}' //打印/etc/fstab文件最后3行以空格作为分隔符的第2列和第4列
二、awk变量
(一)内置变量
- FS:输入字段分隔符,默认为空白字符,可以在program中引用
awk -v FS=':' '{print $1,FS,$3}' /etc/passwd
,注意print中的逗号表示1个空格- awk变量也可以引用Shell变量,便于脚本编写
fs=":" ; awk -v FS=$fs '{print $1,FS,$3}' /etc/passwd
- awk变量也可以引用Shell变量,便于脚本编写
- OFS:输出字段分隔符,默认为空白字符
awk -v FS=':' -v OFS=':' '{print $1,$3,$7}' /etc/passwd
-
RS:输入记录分隔符,指定输入时的换行符,原换行符仍有效
awk -v RS=':' '{print $1}' /etc/passwd
- 注意到当指定以":"作为记录分隔符时,输出每条记录的第1个字段时,除了第1条记录外,其他记录的用户名信息都丢失了。
- 原因在于文件每一行行尾的换行符,当指定其他符号为记录分隔符后,其自身的换行功能得到保留,所以每一行行尾的shell信息和下一行的用户名信息被视作为一条记录,而记录的第一个字段为bash信息,用户名信息没有打印。
-
ORS:输出记录分隔符,输出时用指定符号代替换行符
awk -v RS=':' -v ORS='###' '{print }' /etc/passwd
- 下图红框的两端内容之间没有"###"分隔,证明属于一条记录,出现的换行证明原文件行尾的换行符功能仍旧保留,再次印证上文所述
-
NF:字段数量
-
awk '{print NF,$0}' /etc/fstab
,打印每一行并在行首标明所在行字段数量(以空格为字段分隔符)
-
awk -F: '{print $(NF-1)}' /etc/passwd
,打印每行倒数第2个字段(以冒号为字段分隔符)
-
-
NR:行号
-
awk '{print NR,$0}' /etc/fstab
,打印每行的行号
-
awk '{print NR,$0}' /etc/fstab /etc/inittab
可以同时打印多个文件,但是行号连续编写
-
FNR:各文件分别计数,行号
awk '{print FNR,$0}' /etc/fstab /etc/inittab
- FILENAME:当前文件名
awk '{print FILENAME}' /etc/fstab
-
ARGC:命令行参数的个数
awk 'BEGIN {print ARGC}' /etc/fstab /etc/inittab
每个红框内容被视作一个参数
ARGV:数组,保存的是命令行所给定的各参数
awk 'BEGIN {print ARGV[0]}' /etc/fstab /etc/inittab
awk 'BEGIN {print ARGV[1]}' /etc/fstab /etc/inittab
awk 'BEGIN {print ARGV[2]}' /etc/fstab /etc/inittab
(二)自定义变量
-
自定义变量区分大小写,有两种定义方式
- -v 选项定义
- 在program中定义
-
实验:自定义变量的使用
-
awk -v test='hello, gawk' 'BEGIN{print test}'
,直接使用v选项定义 -
awk 'BEGIN{test="hello, gawk";print test}'
,在program中定义
-
awk -F: '{sex="male";print $1,sex,age;age=18}' /etc/passwd
首行没有第3个字段,因为打印首行第3个字段后才对其变量进行了赋值,之后每一行打印时变量已经赋值,可以正常显示
- 也可以将program部分写入文件中,使用awk的-f选项导入
-
echo '{print script,$1,$2}' > awkscript
awk -F: -f awkscript -v script="awk" /etc/passwd
三、printf命令:实现格式化输出
-
语法:printf "FORMAT", item1, item2, ...
- 必须指定FORMAT
- 不会自动换行,需要显式给出换行控制符:\n
- FORMAT中需要分别为后面每个item指定格式符
-
格式符:与item对应
- %c:显示字符的ASCII码
- %d, %i:显示十进制整数
- %e, %E:显示科学计数法数值
- %f:显示为浮点数
- %g, %G:以科学计数法或浮点形式显示数值
- %s:显示字符串
- %u:无符号整数
- %%:显示%自身
-
修饰符:
- #[.#]:第一个#控制显示的宽度,第二个#表示小数点后精度,%3.1f
- -:左对齐(默认右对齐)%-15s
- +:显示数值的正负符号%+d
-
实验:printf的使用
- 显示/etc/passwd文件以冒号为分隔符的第1,3列,并且第1列宽20左对齐,第3列宽10右对齐
awk -F: '{printf "%-20s %10d\n",$1,$3}' /etc/passwd
- 显示/etc/passwd文件以冒号为分隔符的第1,3列,格式:"Username: 第1列内容 , ID: 第3列内容",其中第1列内容宽15左对齐
awk -F: '{printf "Username: %-15s,ID: %d\n",$1,$3}' /etc/passwd
- 显示/etc/passwd文件以冒号为分隔符的第1,3列,并且第1列宽20左对齐,第3列宽10右对齐
四、操作符
算术操作符:
x+y, x-y, x*y, x/y, x^y, x%y
-x: 转换为负数
+x: 转换为数值赋值操作符:
=, +=, -=, *=, /=, %=, ^=
++, --比较操作符:
==, !=,>, >=, <, <=模式匹配符:
~:左边是否匹配右边
!~:左边是否不匹配右边
示例:
awk -F: '$0 ~ /root/{print $1}' /etc/passwd
打印/etc/passwd文件包含"root"内容行的第一个字段(以":"分隔)
awk '$0 ~ "^root" ' /etc/passwd
打印/etc/passwd文件行首为"root"的行
awk '$0 !~ /root/' /etc/passwd
打印/etc/passwd文件不包含"root"内容的所有行
awk -F: '$3==0' /etc/passwd
打印/etc/passwd文件以":"分隔的第三个字符等于0的所有行逻辑操作符:与&&,或||,非!
示例:
awk -F: '$3>=0 && $3<1000 {print $1}' /etc/passwd
打印root用户和系统用户名称(CentOS 7)
awk -F: '$3==0 || $3>=1000 {print $1}' /etc/passwd
打印root用户和普通用户的名称(CentOS 7)
awk -F: '!($3==0){print $1}' /etc/passwd
打印除root用户外的所有用户名称
awk -F: '!($3>=500) {print $3}' /etc/passwd
打印root用户和系统用户的UID(CentOS 6)函数调用:function_name(argu1, argu2, ...)
条件表达式(三目表达式):
selector?if-true-expression:if-false-expression
示例4-1:
awk -F: '{$3>=1000?usertype="Common User":usertype="Sysadmin or SysUser";printf "%15s: %-s\n",$1,usertype}' /etc/passwd
打印用户名称,若是普通用户则其后输出"Common User",否则输出"Sysadmin or SysUser",中间以":"分割。用户名称宽20右对齐,其后的类型信息左对齐(CentOS 7)
五、awk pattern
- PATTERN:根据pattern条件,过滤匹配的行,再做处理
- 如果未指定:空模式,匹配每一行
(一)正则表达式:/regular expression/
仅处理模式匹配到的行,需要用"/ /"括起来
(二)关系表达式:结果为“真”才会被处理
真:结果为非0值,非空字符串
假:结果为空字符串或0值
(三)行范围
/pat1/,/pat2/:支持使用正则表达式描述,不支持直接给出数字格式
(四)BEGIN/END模式
BEGIN{}:仅在开始处理文件中的文本之前执行一次
END{}:仅在文本处理完成之后执行一次
示例5-1:pattern中正则表达式和关系表达式的用法
-
awk '/^UUID/{print $1}' /etc/fstab
打印/etc/fstab文件以UUID开头的行的第1列
awk '!/^UUID/{print $1}' /etc/fstab
打印/etc/fastab文件不以UUID开头的行的第1列awk -F: 'i=1;j=1{print i,j}' /etc/passwd
本条命令是awk -F: 'i=1{print $0};j=1{print i,j}' /etc/passwd
的简化,program第1条语句关系判断为真(i=1),故打印本行;第2条语句关系判断为真(j=1),故打印i和j值
awk '!0' /etc/passwd; awk '!1' /etc/passwd
打印全部行;一行也不打印awk -F: '$3>=1000{print $1,$3}' /etc/passwd
打印/etc/passwd文件以":"为分隔符的第3列数值大于等于1000的行的第1和第3列awk -F: '$3<1000{print $1,$3}' /etc/passwd
打印/etc/passwd文件以":"为分隔符的第3列数值小于1000的行的第1和第3列awk -F: '$NF=="/bin/bash"{print $1,$NF}' /etc/passwd
打印/etc/passwd文件以":"为分隔符的最后一列是"/bin/bash"的行的第1列和最后1列awk -F: '$NF~ /bash$/{print $1,$NF}' /etc/passwd
打印/etc/passwd文件以":"为分隔符的最后一列是以"bash"作为行尾的行的第1列和最后1列seq 10 | awk 'i=!i'
读入第1行时,i值取反为1所以打印本行,读入第2行时,i值为1取反后为0所以不打印本行,读入第3行与读入第1行相似,读入第4行与读入第2行相似,以此类推,所以结果为打印奇数行
-
seq 10 | awk '!(i=!i)'
或seq 10 | awk -v i=1 'i=!i'
与上文类似,结果为打印偶数行
示例5-2:pattern中行范围和BEGIN/END模式的用法
-
awk -F: '/^root\>/,/^nobody\>/{print $1}' /etc/passwd
打印/etc/passwd文件从行首是"root"单词的行至行首是"nobody"单词的行中以":"作为分隔符的第1列
awk -F: 'BEGIN{printf "%-8s%s\n---------\n","USER","UID"}/^root\>/,/^sync\>/{printf "%-8s%d\n",$1,$3}END{print "========="}' /etc/passwd
BEGIN和END模式适用于打印表头和表尾
六、awk action
- 常用的action分类
(1) Expressions:算术,比较表达式等
(2) Control statements:if, while等
(3) Compound statements:组合语句
(4) input statements
(5) output statements:print等
(一)awk控制语句:if-else
功能:对awk取得的整行或某个字段做条件判断
语法:
if(condition) {statement;…} [else statement]
if(condition1) {statement1} else if(condition2){statement2} else {statement3}示例6-1:
awk -F: '{if($3>=1000)print $1,$3}' /etc/passwd
打印普通用户名称和UID(CentOS 7)
awk -F: '{if($NF=="/bin/bash") print $1}' /etc/passwd
打印/etc/passwd文件以":"为分隔符的最后一列是"/bin/bash"的行的第1列
awk '{if(NF>5) print $0}' /etc/fstab
打印/etc/fstab文件以空格为分隔符字段数大于5的行
awk -F: '{if($3>=1000) {printf "Common User: %s\n",$1} else {printf "Sysadmin or SysUser: %s\n",$1}}' /etc/passwd
普通用户输出"Common User",其他用户输出"Sysadmin or SysUser",中间以":"分割,然后打印用户名称(CentOS 7)
df| awk -F% '/^\/dev\/sd/{print $1}'| awk '$NF>=80{print $1,$NF}'
检查磁盘分区占用率,发现占用率大于等于的分区将分区名称和占用率打印出来
awk -F: '{if($3>=1000){printf "Common User: %s\n",$1} else if($3==0){printf "Sysadmin: %s\n",$1} else{printf "SysUser: %s\n",$1}}' /etc/passwd
普通用户输出"Common User",系统用户输出SysUser",root用户输出"Sysadmin",中间以":"分割,然后打印用户名称(CentOS 7)
(二)awk控制语句:while循环
功能:条件“真”,进入循环;条件“假”,退出循环
语法:while(condition){statement;…}
-
示例6-2:
-
awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {print $i,length($i); i++}}' /etc/grub2.cfg
找出/etc/grub2.cfg文件以不定数量(包含0个)空格作为行首后接字符串"linux16"的行,以空格为分隔符打印这些行每个字段内容和字段长度
-
awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {if(length($i)>=10) {print $i,length($i)}; i++}}' /etc/grub2.cfg
找出/etc/grub2.cfg文件以不定数量(包含0个)空格作为行首后接字符串"linux16"的行,以空格为分隔符找出长度大于10的字段,把字段和字段长度打印出来
-
(三)awk控制语句:do-while循环
功能:无论真假,至少执行一次循环体
语法:do {statement;…}while(condition)
示例6-3:
awk 'BEGIN{ total=0;i=0;do{ total+=i;i++}while(i<=100);print total}
从1到100求和
(四)awk控制语句:for循环
语法:for(expr1;expr2;expr3) {statement;…}
常见用法:
for(variable assignment;condition;iterationprocess) {for-body}特殊用法:能够遍历数组中的元素
语法:for(var in array) {for-body}示例6-4:
awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {print $i,length($i)}}' /etc/grub2.cfg
找出/etc/grub2.cfg文件以不定数量(包含0个)空格作为行首后接字符串"linux16"的行,以空格为分隔符打印这些行每个字段内容和字段长度
- 实验:awk, shell脚本和bc的性能比较
time awk 'BEGIN{sum=0;for(i=1;i<=1000000;i++){sum+=i};print sum}'
time (sum=0;for i in {1..1000000};do let sum+=i;done;echo $sum)
time (sum=0;for ((i=1;i<=1000000;i++));do let sum+=i;done;echo $sum)
time seq -s "+" 1000000 | bc
可以看出awk执行效率最高,bc次之,shell脚本的效率最差
(五)awk控制语句:switch, break, continue, next语句
switch语句
语法:switch(expression) {case VALUE1 or /REGEXP/: statement1; case VALUE2 or /REGEXP2/: statement2; ...; default: statementn}break和continue
awk 'BEGIN{sum=0;for(i=1;i<=100;i++) {if(i%2==0)continue;sum+=i}print sum}'
从1至100的奇数求和
awk 'BEGIN{sum=0;for(i=1;i<=100;i++) {if(i==66)break;sum+=i}print sum}'
从1至66求和next
提前结束对本行处理而直接进入下一行处理(awk自身循环)
awk -F: '{if($3%2!=0) next; print $1,$3}' /etc/passwd
输出UID为偶数的用户名称和UID
七、awk数组
(一)awk数组的基本用法
awk数组均为关联数组:array[index-expression]
index-expression格式:
(1) 可使用任意字符串;字符串要使用双引号括起来
(2) 如果某数组元素事先不存在,在引用时,awk会自动创建此元素,并将其值初始化为“空串”若要判断数组中是否存在某元素,要使用“index in array”格式进行遍历
-
示例7-1:
-
awk '{arr[$0]++;print $0,arr[$0]}' password
输出文件中的每一行,并且输出这是第几次重复本行信息
-
awk '!arr[$0]++' password
删除重复行,可以对比上图发现前8行中的重复行已经消失。
解释:当行信息第一次赋值给数组元素时,数组元素值为空,所以取反为真,输出此行;而当相同信息第二、三......次赋值时,数组元素值大于等于1,取反为假,不输出此行,从而实现不输出重复行。
-
(二)awk数组的遍历
若要遍历数组中的每个元素,要使用for循环
for(var in array) {for-body}
var会遍历array的每个索引示例7-2:
awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";for (inx in weekdays) {print weekdays[inx]}}'
遍历输出weekday数组的值-
实验:
(1)统计处于不同网络状态的tcp连接数分析:首先用
netstat -tan
命令显示tcp连接情况。统计连接数需要考虑问题:第一,如何过滤有效行,可以在awk中的pattern中设置;第二,怎样存储状态名称和累加计数,可以利用awk的关联数组功能,将状态名称作为数组下标,并且数组自加1作为计数器;第三,如何输出每个状态和状态的数量,可以利用for循环的遍历数组功能实现。代码实现如下:
netstat -tan | awk '/^tcp\>/{ip[$NF]++}END{for (idx in ip) {print idx,ip[idx]}}'
(2)统计access_log文件每个ip地址的记录行数,并且输出行数最多的5个ip地址
分析:查看文件格式,发现ip地址位于行首,并且以空格为分隔符正是第1个字段
仿照实验(1)的思路,将$1作为数组下标实现统计不同ip的记录数,最后for循环遍历输出每个ip地址和记录行数。之后用sort排序工具按数字倒叙排序,用head工具输出前五行数据
代码实现如下:
awk '{ip[$1]++}END{for(idx in ip){print idx,ip[idx]}}' access_log | sort -nr -k2 | head -5
八、awk函数
(一)数值处理:
- rand():返回0和1之间一个随机数
awk 'BEGIN{srand();for(i=1;i<=10;i++)print int(rand()*100)}'
输出10个100以内的随机数,注意使用rand函数前先使用srand函数建立种子
(二)字符串处理:
length([s]):返回指定字符串的长度
sub(r,s,[t]):对t字符串进行搜索r表示的模式匹配的内容,并将第一个匹配的内容替换为s
echo "2008:08:08 08:08:08" | awk 'sub(/:/,"-",$1)'
将以空格为分隔符的第1个字段搜索到的第1个":"替换为"-"
输出结果为"2008-08:08 08:08:08"gsub(r,s,[t]):对t字符串进行搜索r表示的模式匹配的内容,并全部替换为s所表示的内容
echo "2008:08:08 08:08:08" | awk 'gsub(/:/,"-",$1)'
将以空格为分隔符的第1个字段搜索到的每个":"替换为"-"
输出结果为"2008-08-08 08:08:08"split(s,array,[r]):以r为分隔符,切割字符串s,并将切割后的结果保存至array所表示的数组中,第一个索引值为1,第二个索引值为2,…
netstat -tan | awk '/^tcp\>/{split($5,ip,":");count[ip[1]]++}END{for (idx in count){print idx,count[idx]}}'
将netstat -tan
输出结果中的Foreign Address列ip地址(不含端口号)和其出现次数统计出来,只能使用一次awk命令
分析:awk实现了以空格为分隔符取Foreign Address列字段$5的功能,再使用split函数再次将字段以":"为分隔符分割。split函数分割的结果存储在数组ip中,而需要之后遍历的信息在数组ip的第一个元素ip[1]中,故将ip[1]再赋值给新的数组count,此后就可以遍历新数组count统计出现次数
(三)自定义函数:
格式:
function name ( parameter, parameter, ... ) {
statements
return expression
}示例8-1:编写一个函数,实现输出两个变量的较大值
将函数的实现写在max.awx文件中,执行时导入文件
文件中代码如下:
function max(var1,var2) {
var1>var2?maxnum=var1:maxnum=var2
return maxnum
}
BEGIN {printf " max num is %d\n",max(a,b)}
执行命令,awx -f max.awx -v a=5 -v b=3
,结果如下
(四)awk调用shell命令:system()
注意:空格是awk中的字符串连接符,如果system中需要使用awk中的变量可以使用空格分隔,或者说除了awk的变量外其他一律用""引用起来。
-
示例8-2:
awk 'BEGIN{system("hostname") }'
相当于在shell中执行hostname命令awk 'BEGIN{score=100; system("echo your score is " score) }'
调用shell命令echo your scre is
,然后输出awk的变量score的值
九、awk脚本
- 可以将awk程序写成脚本,直接调用或执行
注意:当执行awk脚本时,注意需要行首添加
#! /bin/awk -f
示例9-1:编写脚本user.awk,实现分行输出普通用户的用户名和UID(CentOS 7)
代码如下:
运行脚本前给脚本添加执行权限,执行命令#! /bin/awk -f {if($3>=1000)print $1,$3}
./user.awk -F: /etc/passwd
-
向脚本传递参数
格式:
awk_script_file var1=value1 var2=value2... Inputfile注意:在BEGIN过程中不可用。直到首行输入完成以后,变量才可用。可以通过-v 参数,让awk在执行BEGIN之前得到变量的值。命令行中每一个指定的变量都需要一个-v参数
示例9-2:编写脚本uid.awk,输出指定范围UID号的用户名和其UID
代码如下:
#! /bin/awk -f {if ($3>=min && $3<=max)print $1,$3}
运行脚本前给脚本添加执行权限,执行命令
./uid.awk -F: min=10 max=50 /etc/passwd