一、awk相关知识详解
1.awk简介
(1)awk:报告生成器,格式化文本输出
(2)版本:
New awk(nawk)
GNU awk(gawk)
2.基本用法
awk [option] 'program' var=value file...
awk [option] -f programfile var=value file...
awk [option] 'BEGIN{action;...};pattern{action...};END{action...}' file...
说明:
awk程序通常由:BEGIN语句块,能够使用模式匹配的通用语句块,END语句组成
program通常是被单引号引住
option:
-F 指明输入时用到的分隔符,默认为空格(连续空格当做一个空格)
-v var=value 自定义变量
3. awk工作原理
(1)分割符、域、记录相关概念
<1>awk执行时,由分隔符分割的字段(域)标记$1,$2...成为域标识,$0为所有域;和shell中的变量$符含义不同
<2>文件的每一行为一个记录
(2)awk工作原理
第一步:执行BEGIN(action)语块中的语句
第二步:从文件或标准输入读取一行,然后执行pattern{action;...}语句块,它逐行扫描文件,从第一行到最后一行重复这个过程,直到文件全部被读取
第三步:当读至输入流末尾时,执行END(action...)语句块
说明:
BEGIN语句块在awk开始从输入流中读取行之前被执行,这是一个可选的语句块,比如变量初始化、打印输出表格的表头等语句通常可以写在BEGIN语句块中,不可打印多个文件的行参数;只输出一行;
END语句块在awk从输入流中读取完所有的行之后即被执行,比如打印所有行的分析结果这类信息汇总都是在END语句块中完成,它也是一个可选语句块
pattern语句块中的通用命令是最重要的部分,也是可选的。如果没有提供pattern语句块,则默认执行(print $0),即打印每一个读取到的行。
4.awk基本格式
awk [option] 'program' file...
program:pattern{action statements;...}
pattern和action
pattern语句决定动作语句何时触发(BEGIN END)
action statements 对数据进行处理,放在{}指明,(print,printf)
说明:同一个pattern语句块的action之间用“;”隔离,print输出的不同域之间用“,”隔离;省略action,默认执行print $0操作
5.print格式说明
print item1 ,item2....
注意:逗号分隔符
输出的各item可以字符串,也可以是数值;当前记录的字段、变量或awk的表达式
如省略item,相当于print $0
输出内容除了变量,用双引号括起来
举例:
<1> 以“:”为分隔符,显示/etc/passwd每一行信息
awk –F: '{print}' /etc/passwd
<2>以“:”为分隔符,把/etc/passwd每一行以wang信息代替
awk –F: ‘{print “wang”}’ /etc/passwd
<3>以“:”为分割符,显示该文件每一行的第一个字段
awk –F: ‘{print $1}’ /etc/passwd
<4>在标准输入的每一行输出“hello,world”
6.printf命令详解
<1>格式化输出:printf "FORMAT" ,item1,item2,...
必须指定FORMAT
不会自动换行,需要显式给出换行控制符,\n
PARMAT需要分别为后面每个item指定格式符
<2>格式符:与每个item一 一对应
%c:显示字符的ASCII编码
%i:显示十进制整数
%e:显示科学计数法数值
%f:显示为浮点数
%g:以科学计数法或浮点形式显示数值
%s:显示字符串
%u:无符号整数
%%:%本身
<3>修饰符
#[.#]:第一个数字控制显示的宽度,第二个表示小数点后的精度
-:左对齐(默认是右对齐)
+:显示数值的正负符号
<4>实例
awk -F : '{printf "%s",$1}' /etc/passwd
awk -F: ‘{printf "Username: %-15s, UID:%d\n",$1,$3}’ /etc/passwd
疑问:在printf格式化输出中,awk内置变量还有效吗?
发现OFS变量并没有生效,在printf语句中,格式化输出在format设置就好
7.awk变量(引用的时候不用加$符号)
(1)内置变量
<1>FS(field separator):输入字段分割符,默认为空格
awk -F : '{print $1,$3}' /etc/passwd
awk -v FS=":" '{print $1,$3}' /etc/passwd
<2>OFS输出字段分隔符,默认为空格;
OFS默认为空格:此时输出字段间是以空格为分隔符
OFS为“+++”:
<3>RS(record separator)记录输入时分隔符,原换行符仍旧有效
新建文件内容如下
RS为“ ”:此时c d为一行,只不过c后边的原换行符依旧有效,故看起来不在一行
为更深刻的理解RS,看下边两图
说明:因为awk输出字段分割符默认是空格,原来的换行符会被覆盖
<4>ORS:输出记录分割符,用指定符号代替换行符(此时原来的换行符还是有效的)
新建文件内容如下:
ORS为“+++”:
说明:ORS只是把空格转换为“+++”,原先的换行符并没有转换,因为c和e、h后边是由换行符的,但是并没有转换
<5>NF字段数量
awk -F:‘{print NF}’ /etc/fstab
awk -F: '{print $(NF-1)}' /etc/passwd 显示倒数第二个字段
<6>行号 NR
awk '{print NR}' /etc/fstab
awk END'{print NR}' /etc/fstab
<7>FNR 各文件分别统计行号
awk '{print FNR}' test test1
<8>FILENAME 当前文件名
awk '{print FILENAME}' test
<9>ARGC 命令行参数的个数
awk BEGIN'{print ARGC }' test test1
awk '{print ARGC }' test test1
<10>ARGV :数组,保存的是命令行给定的各参数(索引从0开始)
awk ‘BEGIN {print ARGV[0]}’ /etc/fstab /etc/inittab
awk ‘BEGIN {print ARGV[1]}’ /etc/fstab /etc/inittab
(2)自定义变量:
<1>在program中直接定义
<2>-v var=value 变量名区分大小写
awk-v test='hello gawk' '{print test}' /etc/fstab
awk-v test='hello gawk' 'BEGIN{print test}'
awk'BEGIN{test="hello,gawk";print test}'
8.操作符
<1>算术操作符
x+y , x-y , x*y , x/y , x^y , x%y
-x:转换为负数
+x:转换为整数
awk -v x=1 -v y=2 'BEGIN{print x+y}'
<2>赋值操作符
=, +=, -=, *=, /=, %=, ^=
++, --
awk -v a=1 -v b=2 'BEGIN{print b+=a}'
<3>比较操作符
>, >=, <, <=, !=, ==
<4>模式匹配符:
~:左边是否和右边匹配包含 模式匹配的话,被匹配模式需要被/ /括起来
!~:是否不匹配
cat /etc/passwd|awk'$0 ~ /root/'|wc-l
cat /etc/passwd|awk'$0 !~ /root/'|wc-l
<5>逻辑操作符:&& || !
awk –F: '$3>=0 && $3<=1000 {print $1}' /etc/passwd
awk -F: '$3 ==0 || $3>=1000 {print $1}' /etc/passwd
awk -F: ‘!($3==0){print $1}' /etc/passwd
awk -F: '!($3>=500) {print $3}}' /etc/passwd
<6>条件表达式
selector?if-true-expression:if-false-expression
awk -F: '{$3>=1000?usertype="Common User":usertype="Sysadmin or SysUser";printf"%15s:%-s\n",$1,usertype}' /etc/passwd
9.pattern:根据pattern条件,过滤匹配的行,再做处理
<1>如果未指定:空模式,匹配每一行
<2>/regular expression/:仅处理能够匹配到的行
awk'/^UUID/{print $1}' /etc/fstab
awk'!/^UUID/{print $1}' /etc/fstab
<3>relation expression:关系表达式;结果为真才会处理
真:结果非0,非空
假:结果为空字符或0
实例:
awk -F: '$3<1000{print $1,$3}' /etc/passwd
awk -F: '$NF=="/bin/bash"{print $1,$NF}' /etc/passwd
seq 10 | awk 'i=!i' 显示奇数行;显示偶数行的话,提前给i赋值为1即可
awk -F: '""{print}' test 啥都不输出
awk -F: 'i=1;j=1{print $1,$3}' test
<4>line ranges(行范围)
”startline,endline“:/pat1/,/pat2/不支持直接给出数字格式
效果一样:
awk-F: '(NR>=2&&NR<=10){print $1}' /etc/passwd
awk-F: 'NR>=2&&NR<=10{print $1}' /etc/passwd
awk-F: '/^root/,/^nobody/{print $1}' /etc/passwd
<5> BEGIN/END模式
BEGIN{}: 仅在开始处理文件中的文本之前执行一次
END{}:仅在文本处理完成之后执行一次
10.常用的action
<1>Expression:算术,比较表达式
<2>Control Statements :if while 等
<3>compound statements:组合语句
<4>input statements
<5>output statements:print等
11.awk控制语句if-else
if (condition) statement [else statement]
if (condition1) {statement1} else if (condition2) {statement2} else {statement3}
说明:(如果statement为多条命令,必须加“{}”,一条的话,可以不加),一般情况下还是加上大括号比较好
实例:
awk BEGIN'{test=65;if(test>80) {print "it is good"} else if(test<60) {print "you are not ok"} else {print "you are right"}}'
awk-F: '{if($3>=1000)print $1,$3}' /etc/passwd
df-h|awk-F[%] '/^\/dev/{print $1}'|awk'{if($NF>=80) print $1}‘
12.awk控制语句while
while(condition)statement,条件真,进入循环;条件假,退出循环
使用场景:对一行内的多个字段逐一类似处理的时候
对数组中的各元素逐一处理时使用
实例:awk'/^[[:space:]]*linux16/{i=1;while(i<=NF) {print $i,length($i); i++}}' /etc/grub2.cfg
awk'/^[[:space:]]*linux16/{i=1;while(i<=NF) {if(length($i)>=7) {print $i,length($i)}; i++}}' /etc/grub2.cfg
13.awk控制语句do-while
do statement while(condition) 无论真假,至少执行一次
实例: awk 'BEGIN{total=0;i=0;do{total+=i;i++}while(i<=100);print total}'
14.awk控制语句-for循环
for循环
语法:for(expr1;expr2;expr3) statement
for(variable assignment;condition;iterationprocess) {for-body}
特殊用法:能够遍历数组中的元素;
语法:for(varin array) {for-body}
示例:
awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {print $i,length($i)}}' /etc/grub2.cfg
15.awk控制语句switch
语法:switch(expression) {case VALUE1 or /REGEXP/: statement; case VALUE2 or /REGEXP2/: statement; ...; default: statement}
16.awk控制语句break、continue、next
<1>awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i%2==0){continue}{sum+=i}};print sum}'
continue [n]
<2>awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i==11){break}{sum+=i}};print sum}'
break [n]
<3>next 提前结束对本行处理而直接进入下一行处理(awk自身循环)
awk-F: '{if($3%2!=0) next; print $1,$3}' /etc/passwd
17.awk数组
<1>array["index-expression"]
index-expression:
可使用任何字符串,但字符串需要用双引号括起来(变量的话,就不用加了)
如果某数组元素事先不存在,在引用时,awk会自动创建此元素,并将其值初始化为“空串”
若要判断数组中是否存在元素,要使用“index in array”格式进行遍历
实例一:
awk 'BEGIN{week["mon"]="monday";week["tue"]="tuesday";print week["mon"]}'
awk '!a[$0]++' test2
说明:命令三显示的是test2文件的内容,命令三是为了更好地理解“$0”与“arr[$0]”之间的关系,命令一是去掉test2中重复的行;
对命令一来说:应该把“arr[$0]“当做一个整体,例如当做“i”,此处用到i++和++i的相关知 识;arr[aa]=0-->!arr[aa]=1-->arr[aa]=1;arr[bb],arr[cc],arr[dd]类似;当再次到arr[aa]时,arr[aa]=1-->!arr[aa]++=0-->arr[aa]=2;如果第三次遇到arr[aa] 时,arr[aa]=2-->!arr[aa]++=0-->arr[aa]=3;
实例二:统计开放的tcp类型的端口的访问状态
<2>i++和++i的区别
awk 'BEGIN{i=0;print i++,i}'
awk 'BEGIN{i=0;print ++i,i}'
18.awk函数
(1)系统函数
<1>rand():返回0和1之间的一个随机数
实例:
awk'BEGIN{srand(); for (i=1;i<=10;i++)print int(rand()*100) }'
执行awk 'BEGIN{print rand()}'
发现随机数一直一样
执行awk 'BEGIN{srand();print rand()}'
<2>length([s]):返回指定字符串的长度,里边的s是变量
实例:awk ’BEGIN{a="tian";print length(a)} ‘
<3>sub(r,s,[t]):对t字符进行搜索r表示的模式匹配的内容,并将第一个匹配到的内容替换为s
<4>gsub(r,s,[t]):对t字符进行搜索r表示的模式匹配的内容,并将全部内容替换为s
实例:
<5>split(s,array(),[r]):以r为分隔符切割字符s,并将切割后的交过保存至array所表示的数组中(第一个索引值是1,往后依次累加)
实例:netstat-tan | awk'/^tcp\>/{split($5,ip,":");count[ip[1]]++}END{for (i in count) {print i,count[i]}}'
(2).自定义函数
格式:function name (parameter1,parameter2...){
statements
return expression
}
实例:
方法一:#cat fun.awk
function max(v1,v2) {
v1>v2?var=v1:var=v2
return var
}
END{a=3;b=2;print max(a,b);}
#awk –f fun.awktestfile
方法二: #cat fun.awk
function max(v1,v2) {
v1>v2?var=v1:var=v2
return var
}
END{print max(3,2)}
#awk –f fun.awktestfile
方法三:#cat fun.awk
#!/bin/awk -f
function max(v1,v2) {
v1>v2?var=v1:var=v2
return var
}
END{a=3;b=2;print max(a,b);}
#chmod u+x fun.awk
#fun.awk
19.awk中调用shell命令
system(命令)用来在awk命令中调用shell命令;(这是一个函数)
空格是awk中的字符串连接符,如果system中需要使用awk中的变量可以使用空格分隔,
awk BEGIN'{system("hostname") }'
awk'BEGIN{a=12; system("echo " a) }'带颜色部分是用来分离shell命令和awk变量的
20.awk脚本,将awk程序写成脚本,直接调用或执行
直接调用
直接执行
21.向awk脚本传递参数
格式:awk.awk var=value var1=value1... inputfile
实例一:像脚本中传递参数
实例二:向直接执行的脚本中的函数传递参数
小结:发现像实例一 一样直接给函数传递参数是不行的,必须以变量的形式传递参数
二、课后作业
1、统计/etc/fstab文件中每个文件系统类型出现的次数
(1)查看/etc/fstab文件内容
(2)执行如下命令
2、统计/etc/fstab文件中每个单词出现的次数;
(1)查看/etc/fstab文件内容
(2)执行如下命令
3.新建如下文件,分别统计1班和2班的平均分
输入如下命令
发现第二行的命令是正确的,因为第一行的命令没读取一行数据,s1和s2被重新赋值一次