一、awk简介
awk是由3个人开发的:Aho, Weinberger, Kernighan;该功能主要是用来生成报告和格式化文本输出。它有多个版本:New awk(nawk),GNU awk( gawk)。我们通常在linux上使用的awk是基于GNU的awk。awk不只是一个命令,其真实,为一门语言,循环、数组、条件判断样样功能都有,功能非常强大,当然功能强大了,那么必定复杂。
二、基本用法:
awk [options] ‘program’ var=value file…
awk [options] ‘BEGIN{ action;… } pattern{ action;… } END{action;… }’ file …
awk [options] -f ‘a program file’ file … (读取一个存有awk规则预存文件,再对指定文件进行处理) 这种用法,最后再说
options:
-F 指明输入时用到的字段分隔符(awk默认的分隔符就是空白字符)
-v var=value: 自定义变量 (awk的变量也分内置变量和自定义变量两种)
内置变量:
FS:输入字段分隔符,默认为空白字符
OFS:输出字段分隔符,默认为空白字符
RS:输入记录分隔符,指定输入时的换行符
ORS:输出记录分隔符,输出时用指定符号代替换行符
NF:字段数量
NR:记录号
FNR:各文件分别计数,记录号
FILENAME:当前文件名
ARGC:命令行参数的个数
ARGV:数组,保存的是命令行所给定的各参数
注意:虽然为内置变量,但是内置变量也可以用-v来声明初始值
1、awk [options] ‘program’ var=value file…用法的详细说明
program: pattern{action statements;..} #program由pattern和action两部分来组成
pattern和action:
• pattern部分决定动作语句何时触发及触发事件(也可以理解为匹配,用来过滤记录的)BEGIN,END
• action statements对数据进行处理,放在{}内指明print, printf
分割符、域和记录:
• awk执行时,由分隔符分隔的字段(域)标记$1,$2..$n称为域标识。$0为所有域,注意:和shell中变量$符含义不同
• 文件的每一行称为记录(一般的一行被称作一条记录)
• 省略action,则默认执行 print $0 的操作($0表示一整条记录,print表示打印输出的意思)
实例1,显示源输出的指定列:
- [root@newhostname /]# df | grep /dev/sd #先查看一下源标准输出
/dev/sda1 1038336 162080 876256 16% /boot
/dev/sdb3 3135488 135364 3000124 5% /mnt/xfs
/dev/sdb2 3030800 9236 2847896 1% /mnt/ext4
[root@newhostname /]# df | grep /sd | awk ‘{print $1”===”$5}’
/dev/sda1===16%
/dev/sdb3===5%
/dev/sdb2===1%
#$1为输出的第一列,$5为第5列,awk的默认字符是空表字符, 两列中间的等号为加入的字符串“===”
实例2,使用awk的内置变量:
- [root@newhostname /]# awk -v FS=’:’ ‘{print $1,FS,$3}’ /etc/passwd | head -5 #我们使用内置变量FS来作为分隔符
root : 0
bin : 1
daemon : 2
adm : 3
lp : 4
[root@newhostname /]# awk -F: ‘{print $1,FS,$3}’ /etc/passwd | head -5 #我们使用-F option来指定分隔符
root : 0
bin : 1
daemon : 2
adm : 3
lp : 4
由上面两个结果来看,其意义是一样的, 可以使用 -v 直接声明字段的分隔符,也可以用-F来指定。而在action内,变量是可以直接被调用的 ,注意,action内不要看走眼,如果是字符串的话,需要加“”“,直接输入的字符会当做变量来处理。
OFS变量的作用:
- [root@newhostname /]# awk -v FS=’:’ -v OFS=’:’ ‘{print $1,$3,$7}’ /etc/passwd | head -5 #OFS输出字段分隔符,默认为空白,这里我们指定为“:”
root:0:/bin/bash
bin:1:/sbin/nologin
daemon:2:/sbin/nologin
adm:3:/sbin/nologin
lp:4:/sbin/nologin
[root@newhostname /]# awk -v FS=’:’ ‘{print $1,$3,$7}’ /etc/passwd | head -5 #我们不指定OFS两段命令一对比,很明显,下面输出的使用的默认输出分隔符为空白。
root 0 /bin/bash
bin 1 /sbin/nologin
daemon 2 /sbin/nologin
adm 3 /sbin/nologin
lp 4 /sbin/nologin
RS、ORS变量的应用:
- [root@newhostname zsfile]# cat 123.txt #文件的原输出
dddd:112233:1000:1000 /home/bash:/bin/bash[root@newhostname zsfile]# awk -v RS=’:’ ‘{print }’ ./123.txt #定义RS变更记录分隔符(默认的分隔符是换行符)
dddd
112233
1000
1000 /home/bash
/bin/bash[root@newhostname zsfile]# awk -v RS=’:’ -v ORS=’|-|’ ‘{print }’ ./123.txt #我们定义ORS来改变输出的分隔符
dddd|-|112233|-|1000|-|1000 /home/bash|-|/bin/bash
|-|[root@newhostname zsfile]#
RS的意义是改变处理文本之前的记录分隔符,当处理完记录之后,输出记录分隔符(ORS)如果未定义,则还是现实原来默认的记录分隔符去显示,即默认的换行符。
实例3,NF字段数量,NR行号变量,FNR分文件行号变量,FILENAME当前的文件名,ARGC命令行参数的个数
- [root@newhostname zsfile]# vim 1111
[root@newhostname zsfile]# cat 1111
aa bb cc dd ee
11 22 33 44 55
33 22 11 ss 44
[root@newhostname zsfile]# awk ‘{print NR,NF}’ 1111 #NR表示第几行,NF为列的总数
1 5
2 5
3 5[root@newhostname zsfile]# awk ‘{print NR,$NF}’ 1111 #$NF表示最后一列
1 ee
2 55
3 44[root@newhostname zsfile]# awk ‘{print NR,$NF}’ 1111 123.txt #如果使用awk同时对多个文件进行处理,那么使用NR,最后的出列结果会合并成1个,而不会按文件分开,如果要按文件分开,要是用FNR
1 ee
2 55
3 44
4 /home/bash:/bin/bash[root@newhostname zsfile]# awk ‘{print FNR,$NF}’ 1111 123.txt #使用FNR按文件分开
1 ee
2 55
3 44
1 /home/bash:/bin/bash[root@newhostname zsfile]# awk ‘{print FILENAME,FNR,$NF}’ 1111 123.txt #FILENAME 在输出列输出文件的名字
1111 1 ee
1111 2 55
1111 3 44
123.txt 1 /home/bash:/bin/bash[root@newhostname zsfile]# awk ‘{print ARGC}’ 1111 #awk 表示一个参数 1111表示一个参数
2
2
2
[root@newhostname zsfile]# awk ‘{print ARGC}’ 1111 123.txt #awk表示1个参数,1111表示一个参数,123.txt表示一个参数,所以输出结果为“3”
3
3
3
3[root@newhostname zsfile]# awk ‘BEGIN {print ARGC}’ /etc/fstab /etc/inittab
3
变量是可以在action内定义的,但是不利于写shell脚本,因为一般action都是有单引号引起来的,在shell内单引号是强引用,无法很好地使用shell变量(是shell变量非awk变量)
printf的用法(格式化字符串输出,比print更强大的输出方式,printf实际上也是一个linux命令):
printf的格式和特性:printf “FORMAT”, item1, item2, …
(1) 必须指定FORMAT
(2) 不会自动换行,需要显式给出换行控制符,\n
(3) FORMAT中需要分别为后面每个item指定格式符
printf的格式符:
%c: 显示字符的ASCII码
%d, %i: 显示十进制整数
%e, %E:显示科学计数法数值
%f:显示为浮点数
%g, %G:以科学计数法或浮点形式显示数值
%s:显示字符串
%u:无符号整数
%%: 显示%自身
修饰符:
#[.#]:第一个数字控制显示的宽度;第二个#表示小数点后精度,%3.1f
-: 左对齐(默认右对齐,对字符串你操作) %-15s
+:显示数值的正负符号 %+d
下面我们来用printf来举例:
printf字符串的格式化
- [root@joker-6-01 ~]# awk -F: ‘{printf “%s - “,$1}’ /etc/passwd #printf 后面的%s代表的是$1,在这里$1不仅可以是列,还可以是变量、字符串
root - bin - daemon - adm - lp - sync - shutdown - halt - mail - uucp - operator - games - gopher - ftp - nobody - dbus - usbmuxd - rpc - rtkit - avahi-autoipd - vcsa - abrt - rpcuser - nfsnobody - haldaemon - ntp - apache - saslauth - postfix - gdm - pulse - sshd - tcpdump - ee - rr - wang - wang1 - wang2 - wang3 - [root@joker-6-01 ~]#由以上输出我们可以看出,printf是不会换行的,如果要换行 printf引号呢最后要添加\n
[root@joker-6-01 ~]# awk -F: ‘{printf “%s - \n”,$1}’ /etc/passwd | head -5 #添加\n,(因为条目太多,所以在这里我把条目限定在了5行,后面的示例也都这样操作)
root -
bin -
daemon -
adm -
lp -每一条记录执行完都会换行
printf的使用修饰符
- [root@joker-6-01 ~]# awk -F: ‘{printf “Username:%s,UID:%d\n”,$1,$3}’ /etc/passwd | head -5
Username:root,UID:0
Username:bin,UID:1
Username:daemon,UID:2
Username:adm,UID:3
Username:lp,UID:4在这里我们使用字符串的格式化输出,但是输出结果并不直观,下面我们可以使用修饰符进行分割。
[root@joker-6-01 ~]# awk -F: ‘{printf “Username:%-20sUID:%d\n”,$1,$3}’ /etc/passwd | head -5 #%-20s :20表示最大长度,“-”表示左对齐
Username:root UID:0
Username:bin UID:1
Username:daemon UID:2
Username:adm UID:3
Username:lp UID:4输出的结果很明显
[root@joker-6-01 ~]# awk -F: ‘{printf “Username:%20sUID:%d\n”,$1,$3}’ /etc/passwd | head -5 #不加“-”的表示默认右对齐,在这里只是一个示例,没有任何意义
Username: rootUID:0
Username: binUID:1
Username: daemonUID:2
Username: admUID:3
Username: lpUID:4在这种环境下,使用右对齐显示不是我们要的,在这里我只是想标清楚,什么是左对齐,什么是右对齐
接下来我们示范一下浮点数的表示方法
[root@joker-6-01 ~]# awk -F: ‘BEGIN{printf”%f\n”,3.1234}’
3.123400
[root@joker-6-01 ~]# awk -F: ‘BEGIN{printf”%f\n”,3.1}’
3.100000使用%f默认会甩出6位的小数位,那么我们使用修饰符来限定位数,关于BEGIN,在这里的意思是只执行一遍指令,并且不需要读取文件,BEGIN的真正含义是在读取文件之前执行的操作,我们在这里先不管,后面会讲解
[root@joker-6-01 ~]# awk -F: ‘BEGIN{printf”%8.3f\n”,3.1}’ #8代表整数字符的宽度;.3代表小数的位数
3.100
[root@joker-6-01 ~]# awk -F: ‘BEGIN{printf”%08.3f\n”,3.1}’ #08指如果整数位不足8位用0来代表空白
0003.100浮点数的修饰一般我们做普通运维用不到,一般用的比较多的还是%s %d这两个
AWK的运算符:在action内执行
算术操作符:
x+y, x-y, xy, x/y, x^y, x%y
-x: 转换为负数
+x: 转换为数值
字符串操作符:没有符号的操作符,字符串连接
赋值操作符:
=, +=, -=, =, /=, %=, ^=
++, —
比较操作符:
==, !=, >, >=, <, <=
模式匹配符:
~:左边是否和右边匹配包含 !~:是否不匹配
逻辑操作符:
与&&,或||,非!
不多说了,直接举例:
- [root@joker-6-01 ~]# awk -F: ‘$0 ~ /root/{print $1}’ /etc/passwd # “~” 匹配运算符
root
operator大括号前面的这一部分,代表的就是最开始所说的“pattern”,这部分的的实际作用就是用来匹配指定记录。上面的含义就是 $0(整条记录)内是否有匹配root的记录,我们在这里使用的“root”是一个实际的字符串,在这里我们也可以使用正则表达式来匹配,我们使用/regex/的形式来表示匹配,我们来做个演示。
[root@joker-6-01 ~]# cat /etc/passwd | tail -10 #现实passwd的后10行,我们将对这段字符进行操作
gdm:x:42:42::/var/lib/gdm:/sbin/nologin
pulse:x:497:496:PulseAudio System Daemon:/var/run/pulse:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin
ee:x:500:500:dd,ff,aa,qwe:/home/ee:/bin/bash
rr:x:501:502::/home/rr:/bin/bash
wang:x:1010:1010::/app/wangs:/bin/bash
wang1:x:1014:1011::/home/wang1:/bin/bash
wang2:x:1012:1012::/home/wang2:/bin/bash
wang3:x:1013:1013::/home/wang3:/bin/bash[root@joker-6-01 ~]#cat /etc/passwd | tail -10| awk ‘$0 ~ /^t.+n$/{print $0}’
tcpdump:x:72:72::/:/sbin/nologin“/^t.+n$/” 代表的开头是“t”结尾为“n”的记录。
我们再来做一个不配“t”结尾为“n”的记录的示例:
[root@joker-6-01 ~]# cat /etc/passwd | tail -10 | awk ‘$0 !~ /^t.+n$/{print $0}’ # “!~” 表示不匹配,也就是说,不匹配的记录,awk认为是我们需要的数据
gdm:x:42:42::/var/lib/gdm:/sbin/nologin
pulse:x:497:496:PulseAudio System Daemon:/var/run/pulse:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
ee:x:500:500:dd,ff,aa,qwe:/home/ee:/bin/bash
rr:x:501:502::/home/rr:/bin/bash
wang:x:1010:1010::/app/wangs:/bin/bash
wang1:x:1014:1011::/home/wang1:/bin/bash
wang2:x:1012:1012::/home/wang2:/bin/bash
wang3:x:1013:1013::/home/wang3:/bin/bash我们再action内做算数运算
[root@joker-6-01 ~]# awk ‘BEGIN{print 2+2}’
4
[root@joker-6-01 ~]# awk ‘BEGIN{print 23}’
6
[root@joker-6-01 ~]# awk ‘BEGIN{print 2%3}’
2
[root@joker-6-01 ~]# awk ‘BEGIN{print 2*3}’
8然后我这里有个坑:
[root@joker-6-01 ~]# awk ‘BEGIN{i=0;print ++i,i}’ #限制性i+1 然后在输出i
1 1
[root@joker-6-01 ~]# awk ‘BEGIN{i=0;print i++,i}’ #先输出 i 然后再执行 i+1,所以这两个结果不同
0 1[root@joker-6-01 zsfile]# awk -F: ‘$3 == 0’ /etc/passwd #以:为分隔符,如果第三列为0,那么打印这一样,省略action则表示打印整条记录
root:x:0:0:root:/root:/bin/bash逻辑运算符的使用方法
[root@joker-6-01 zsfile]# awk -F: ‘$3 >=0 && $3<=100{print $1}’ /etc/passwd #uid大于等于0,小于等于100的记录,打印第一列
root
bin
daemon
adm
lp
sync
shutdown
halt
uucp
operator
games
gopher
ftp
nobody
dbus
rpc
vcsa
rpcuser
haldaemon
ntp
apache
postfix
gdm
sshd
tcpdump[root@joker-6-01 zsfile]# awk -F: ‘!($3==0) && NR==3 {print $1}’ /etc/passwd #这句表达的意思:取第三行,如果第三列不等于0,就打印该行,否则不操作。
daemon
2、awk [options] ‘BEGIN{ action;… } pattern{ action;… } END{action;… }’ file …的详细使用方法
BEGIN/END模式
BEGIN{}: 仅在开始处理文件中的文本之前执行一次
END{}:仅在文本处理完成之后执行一次
示例:
- [root@joker-6-01 zsfile]# awk -F : ‘BEGIN {printf “USER %-16sUSERID\n”,””}/^(t|p)/ {printf “%-20s %s\n”,$1,$3}END{printf “end%-18sfile\n”,””}’ /etc/passwd
执行结果:
USER USERID #BEGIN开始打印一行
postfix 89
pulse 497
tcpdump 72
end file #END执行完后打印if的示例:
三、awk的高阶用法
一般这种模式是用来处理一些判断或者循环事件的。
1、awk的判断语句(一般在action内使用)
awk控制语句——if-else
if(condition){statement;…}[else statement]
if(condition1){statement1}else if(condition2){statement2}else{statement3}
awk的三元表达式,可用作变量赋值的判断
selector?if-true-expression:if-false-expression
- [root@joker-6-01 zsfile]# awk -F: ‘{if($3 >=100)print $1,$3}’ /etc/passwd #判断如果第三列大于100(passwd中的uid值),打印这个记录的第一列和第三列
usbmuxd 113
rtkit 499
avahi-autoipd 170
abrt 173
nfsnobody 65534
saslauth 498
pulse 497我们定义两个变量,分别判断两个值的大小
[root@joker-6-01 zsfile]# awk ‘BEGIN{a=1;b=2;if(a>b){print “ok”}else if(a==b){print”a=b”}else {print “no”}}’
no
[root@joker-6-01 zsfile]# awk ‘BEGIN{a=2;b=2;if(a>b){print “ok”}else if(a==b){print”a=b”}else {print “no”}}’
a=b
[root@joker-6-01 zsfile]# awk ‘BEGIN{a=2;b=1;if(a>b){print “ok”}else if(a==b){print”a=b”}else {print “no”}}’
ok三元表达式的用法:
[root@joker-6-01 zsfile]# awk ‘BEGIN{a=1;b=2;a>b?a=3:b=3;print a,b}’
1 3
2、while循环
while(condition){statement;…}
do-while循环
do {statement;…}while(condition)
这种形式的循环表达的意思是,无论什么样的条件都会先执行一次循环,然后再判断while条件的真假。
3、for循环
for(expr1;expr2;expr3) {statement;…}
for(variable assignment;condition;iteration process){for-body}
- 两种语法:1、for i in array #这种方法,待会写了数组再谈
2、for( i=x;i<=100;i++)[root@newhostname zsfile]# awk ‘/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {print $i,length($i)}}’ /boot/grub2/grub.cfg #遍历一个文件
linux16 7
/vmlinuz-3.10.0-693.el7.x86_64 30
root=/dev/mapper/centos_joker—7—01-root 41
ro 2
rd.lvm.lv=centos_joker-7-01/root 32
rd.lvm.lv=centos_joker-7-01/swap 32
rhgb 4
quiet 5
linux16 7
/vmlinuz-0-rescue-77b790ce63d24178bd4d95027a1bd2e9 50
root=/dev/mapper/centos_joker—7—01-root 41
ro 2
rd.lvm.lv=centos_joker-7-01/root 32
rd.lvm.lv=centos_joker-7-01/swap 32
rhgb 4
quiet 5
4、switch语句(相当于shell内的case语句)
switch(expression) {case VALUE1 or /REGEXP/:statement1; case VALUE2 or /REGEXP2/: statement2;…; default: statementn}
break [n] 跳出整个循环
continue [n] 跳过本次循环执行下次循环
next 提前结束对本行处理而直接进入下一行处理(awk自身循环)
- [root@newhostname zsfile]# awk -F: ‘{if($3==0) next; print $1,$3}’ /etc/passwd | head -5 #打印非root用户
bin 1
daemon 2
adm 3
lp 4
sync 5
这些控制语句是不是很眼熟,不论是什么语言都会有这些,只不过是表达形式的不同而已,原理都是一样的。
5、数组
awk的数组也分有序数组和关联数组两种
如果某数组元素事先不存在,在引用时,awk会自动创建此元素,并将其值初始化为“空串”
awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";print weekdays["mon"]}' #定义一个数组,并打印
awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";for(i in weekdays) {print weekdays[i]}}' #便利一个数组的下标,注意,数组名不加[]表示数组下标的序列
6、awk的函数(常用的)
rand():返回0和1之间一个随机数,想用多少为的随机数只需init(rand()*num)即可,注意,如果要调用rand()函数,那么前面必须调用srand(),初始化一次随机值
length([s]):返回指定字符串的长度
sub(r,s,[t]):对t字符串进行搜索r表示的模式匹配的内容,并将第一个匹配的内容替换为s
gsub(r,s,[t]):对t字符串进行搜索r表示的模式匹配的内容,并全部替换为s所表示的内容
split(s,array,[r]):以r为分隔符,切割字符串s,并将切割后的结果保存至array所表示的数组中,第一个索引值为1,第二个索引值为2,…
[root@newhostname zsfile]# awk 'BEGIN{srand(); for (i=1;i<=10;i++)print int(rand()*100) }' #输出10个100以内的随机数
52
68
13
38
95
28
59
45
84
19
[root@newhostname zsfile]# echo "2008:08:08 08:08:08" | awk 'gsub(/:/,"-",$1)' #查找替换全部的:为-
2008-08-08 08:08:08
[root@newhostname zsfile]# echo "2008:08:08 08:08:08" | awk 'sub(/:/,"-",$1)' #查找替换第一个:为-
2008-08:08 08:08:08
[root@newhostname zsfile]# netstat -tan | awk '/^tcp\>/{split($5,ip,":");count[ip[1]]++}END{for (i in count) {print i,count[i]}}' #将第5列分割的字符串保存到数组ip中,并将有ip的下标变量付给count数组, 用for i in 遍历这个count并打印出ip
0.0.0.0 4
172.18.101.180 1</pre>
<p style="padding-left: 60px;">7、awk函数</p>
<p style="padding-left: 90px;">格式:
function name ( parameter, parameter, ... ) {
statements
return expression
}</p>
<p style="padding-left: 60px;">和其他语言的格式基本相同,用法也差不多,这里不多说了。</p>
<p style="padding-left: 60px;">8、awk中调用shell命令,可以使用system()函数</p>
<pre class="EnlighterJSRAW" data-enlighter-language="null">[root@newhostname zsfile]# awk BEGIN'{system("hostname") }'
newhostname
四、最后了,我们现在再说这种用法 :awk [options] -f ‘a program file’ file …
其实很简单就是讲awk的语句存到文件内,然后使用 awk -f来调用,就像shell调用shell脚本一样
cat test_awk.awk
#! /usr/bin/awk
{if($3>=1000)print $1,$3}
[root@newhostname zsfile]# awk -f test_awk.awk /etc/passwd
systemd-network:x:192:192:systemd Management:/:/sbin/nologin
dbus:x:81:81:System bus:/:/sbin/nologin
polkitd:x:999:997:User polkitd:/:/sbin/nologin
colord:x:998:996:User colord:/var/lib/colord:/sbin/nologin
libstoragemgmt:x:997:994:daemon for
pulse:x:171:171:PulseAudio Daemon:/var/run/pulse:/sbin/nologin
tss:x:59:59:Account by
geoclue:x:994:989:User geoclue:/var/lib/geoclue:/sbin/nologin
rpcuser:x:29:29:RPC User:/var/lib/nfs:/sbin/nologin
nfsnobody:x:65534:65534:Anonymous User:/var/lib/nfs:/sbin/nologin
sssd:x:993:988:User sssd:/:/sbin/nologin
输出一个文件内最大长度字段的长度,和这个字段的值
[root@newhostname shell]# cat longest_field.sh
#!/usr/bin/env awk -f
#
#********************************************************************
#encoding -*-utf8-*-
#Author: zhangshang
#Date: 2018-01-04
#URL: http://blog.vservices.top/myblog
#QQ Numbers: 765030447
#********************************************************************
BEGIN {
max=0
}
{
split($0,field_record,FS);
for(i in field_record)
{if(length(field_record[i])>max){
max=length(field_record[i]);max_field=field_record[i]}
}
}
END{
print max_field,max
}
#max=i;
[root@newhostname shell]# cat ~/112233 #查看文本
aaa:asdf:ddddddddd
z:asdfasdfasd:asdfasdfasdfasdf:asdfz:ddd
cccccccccccccccccc:eeeeeee:1111111:
[root@newhostname shell]# awk -F: -f longest_field.sh ~/112233 #awk调用脚本
cccccccccccccccccc 18