标签:awk用法 编程语言
awk概述
AWK的名字来自于其创始人Aho, Weinberger, Kernihan三人的名字首字母的组合。
awk是一个功能非常强大的文本处理工具,它能把文本当做数据库,然后把数据库中的每一行切分为多个字段,可以实现分别对多个字段或行进行处理,并使之按照一定的格式输出,所以awk是文本报告生成器,它能格式化文本。而要选择性地处理字段或行,则可以使用模式(PATTERN)来匹配。
此外,awk已经是一门独立完整的编程语言,它支持一般编程语言所具备的特性,如支持变量、数组等数据结构、支持条件判断及循环等功能、具有内置函数等。而正如前面所说,awk的主要是作为文本报告生成器来使用的。
gawk是GNU project的awk解释器的开放源代码实现,而nawk (new awk)则是20世纪80年代发展起来的新版本,不管是gawk还是nawk都是在其旧版本oawk (old awk)完善一些功能特性而来的。因为大家更倾向于使用awk及gawk,因此本文介绍的是gawk。
2. gawk工作机制
awk的工作流程首先是先读取文本文件中的一行,并对这一行切分为多个字段,将每个字段都存放至awk的内置变量($1,$2,$3,...)中,而当前处理的一行则存放在awk内置变量$0中。接着awk根据用户指定的模式(PATTERN),分别对行或每一行中的字段进行匹配,并根据用户指定的动作语句(Action)对匹配到的行或字段进行加工处理;最后awk会将加工处理的结果默认输出至标准输出,并开始读取文本文件的下一行进行处理,以此类推。
awk就是这样来实现强大的文本处理功能的。不难想到,用户可以通过模式匹配(PATTERN)选择要处理的行或字段,而在动作语句(Action)中指明要如何加工处理数据、数据以什么格式输出等,如果有需要还可以利用Action中的条件判断语句作进一步选择要处理和输出的数据,也可以通过循环语句实现对每一行字段间的遍历或数组的各个元素的遍历等。需要注意的是,awk具有内生循环,因此会自动遍历文本文件中的每一行。
3. gawk命令介绍
命令简述:
gawk - pattern scanning and processing language
gawk是文本处理工具,支持模式扫描,是一门编程语言。
语法格式:
gawk [options] ‘program‘ FILE ...
常用选项:
-F:用于指定输入时用到的字段分隔符;
-v var=value:自定义变量;
program:
program:PATTERN{ACTION STATEMENTS}
PATTERN:模式
ACTION STATEMENTS:动作语句
PATTERN:
(1) empty:空模式,匹配每一行;
(2) /regular expression/:仅处理被此处的模式(正则表达式)所匹配到的行;
(3) relational expression:关系表达式;结果有“真”有“假”,结果为“真”时才执行;
(4) lines ranges:指定行范围;
(5) BEGIN/END模式:
BEGIN{}:仅在开始处理文件中的文本之前执行一次;
END{}:仅在文本处理完成之后、命令结束之前执行一次;
我们从netstat命令中提取了如下信息作为使用示例:
[root@localhost ~]# cat netstat.txt Proto Recv-Q Send-Q Local-Address Foreign-Address State tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN tcp 0 0 10.10.10.140:49808 10.10.10.140:80 TIME_WAIT tcp 0 52 10.10.10.140:22 10.10.10.1:52641 ESTABLISHED tcp 0 0 10.10.10.140:22 10.10.10.1:51926 ESTABLISHED tcp 0 0 10.10.10.140:22 10.10.10.1:52640 ESTABLISHED tcp 0 0 10.10.10.140:49806 10.10.10.140:80 TIME_WAIT tcp 0 0 10.10.10.140:49804 10.10.10.140:80 TIME_WAIT tcp 0 0 10.10.10.140:49810 10.10.10.140:80 TIME_WAIT tcp 0 0 10.10.10.140:49812 10.10.10.140:80 TIME_WAIT
要点:
1. $1,$2,...$n分别表示当前处理的一行的第1个、第2个...第n个字段。
2. 使用print语句时,在命令行可用逗号分隔各个字段,在输出时默认以空白字符作为分隔符。
(1) empty:空模式,匹配每一行
示例:
显示第1列、第4列:
[root@localhost ~]# awk ‘{print $1,$4}‘ netstat.txt Proto Local-Address tcp 0.0.0.0:22 tcp 127.0.0.1:25 tcp 10.10.10.140:49808 tcp 10.10.10.140:22 tcp 10.10.10.140:22 tcp 10.10.10.140:22 tcp 10.10.10.140:49806 tcp 10.10.10.140:49804 tcp 10.10.10.140:49810 tcp 10.10.10.140:49812
因为是空模式,所以会匹配每一行。
(2) /regular expression/:仅处理被此处的模式(正则表达式)所匹配到的行;
示例:
显示netstat.txt文件中以tcp开头的行:
[root@localhost ~]# awk ‘/^tcp\>/‘ netstat.txt tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN tcp 0 0 10.10.10.140:49808 10.10.10.140:80 TIME_WAIT tcp 0 52 10.10.10.140:22 10.10.10.1:52641 ESTABLISHED tcp 0 0 10.10.10.140:22 10.10.10.1:51926 ESTABLISHED tcp 0 0 10.10.10.140:22 10.10.10.1:52640 ESTABLISHED tcp 0 0 10.10.10.140:49806 10.10.10.140:80 TIME_WAIT tcp 0 0 10.10.10.140:49804 10.10.10.140:80 TIME_WAIT tcp 0 0 10.10.10.140:49810 10.10.10.140:80 TIME_WAIT tcp 0 0 10.10.10.140:49812 10.10.10.140:80 TIME_WAIT
显示/etc/fstab文件中以‘UUID‘开头的行的第一个字段:
[root@localhost ~]# awk ‘/^UUID/{print $1}‘ /etc/fstab UUID=60eb0d1c-9834-4348-9a79-2f91983a8ede UUID=3995456f-a3e9-4b69-a0de-0fd48068da39 UUID=01d24b59-eee4-472e-9985-ef44fd5e059c [root@localhost ~]#
显示/etc/fstab文件中不以‘UUID‘开头的行的第一个字段:
[root@localhost ~]# awk ‘!/^UUID/{print $1}‘ /etc/fstab # # # # # # # tmpfs devpts sysfs proc /dev/sr0 #/dev/md0 [root@localhost ~]#
注意:对过滤模式取反,只需在模式前加上‘!‘即可。
(3) relational expression:关系表达式;结果有“真”有“假”,结果为“真”时才执行
什么才是“真”?结果为非0值、非空字符串,即为真。
示例:
显示/etc/passwd中最后一个字段为‘/bin/bash‘的用户的用户名、id及shell:
方式一: [root@localhost ~]# awk -F: ‘$NF=="/bin/bash"{print $1,$3,$NF}‘ /etc/passwd root 0 /bin/bash logstash 500 /bin/bash centos 501 /bin/bash 方式二: [root@localhost ~]# awk -F: ‘$NF~/\/bin\/bash$/{print $1,$3,$NF}‘ /etc/passwd root 0 /bin/bash logstash 500 /bin/bash centos 501 /bin/bash
(4) lines ranges:指定行范围
格式:
startline,endline:/pat1/,/pat2/
注意:不支持直接给出数字的格式,只能使用模式进行匹配;
示例:
显示/etc/passwd文件中第2行到第10行的第一个字段:
[root@localhost ~]# awk -F: ‘(NR>=2&&NR<=10){print $1}‘ /etc/passwd bin daemon adm lp sync shutdown halt mail uucp [root@localhost ~]#
错误示例:
[root@localhost ~]# awk -F: ‘2,10{print $1}‘ /etc/passwd
注意这是错误写法!因为不能直接给出行数!
(5) BEGIN/END模式:
BEGIN{}:仅在开始处理文件中的文本之前执行一次;
END{}:仅在文本处理完成之后、命令结束之前执行一次;
示例:
指定分隔符为‘:‘:
[root@localhost ~]# awk ‘BEGIN{FS=":"}{print $1,$3,$6}‘ /etc/passwd root 0 /root bin 1 /bin daemon 2 /sbin adm 3 /var/adm lp 4 /var/spool/lpd sync 5 /sbin shutdown 6 /sbin halt 7 /sbin mail 8 /var/spool/mail
统计出当前主机的tcp连接状态为"LISTEN"的连接数:
[root@localhost ~]# netstat -tan Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN tcp 0 52 10.10.10.140:22 10.10.10.1:51926 ESTABLISHED tcp6 0 0 :::80 :::* LISTEN tcp6 0 0 :::22 :::* LISTEN tcp6 0 0 ::1:25 :::* LISTEN [root@localhost ~]# [root@localhost ~]# netstat -tan | awk ‘$NF=="LISTEN"{i++}END{print i}‘ 5 [root@localhost ~]#
说到内建变量,我们来看一看awk的内建变量吧:
FS | input field seperator,输入时的字段分隔符,默认为空白字符 |
OFS | output field seperator,输出时的字段分隔符,默认为空白字符 |
RS | input record seperator,输入时的换行符,默认为\n |
ORS | output record seperator,输出时的换行符,默认为\n |
NF | number of field,每一行的字段数量 |
NR | number of record,行数,显示为当前处理的行的行号 |
FNR | file number of record,行数,各个文件分开进行计数 |
FILENAME | 当前文件名 |
ARGC | 命令行给定的参数个数 |
ARGV | 数组,保存的是命令行给定的各参数 |
示例:
显示/etc/passwd文件中每一行的第一个字段:
[root@localhost ~]# awk -v FS=":" ‘{print $1}‘ /etc/passwd root bin daemon adm lp sync shutdown halt
显示效果与直接用-F选项指出分隔符为‘:‘相同。
显示/etc/passwd文件中每一行的第1、3、7个字段,并且每个字段之间用冒号连接:
[root@localhost ~]# awk -v FS=":" -v OFS=":" ‘{print $1,$3,$7}‘ /etc/passwd root:0:/bin/bash bin:1:/sbin/nologin daemon:2:/sbin/nologin adm:3:/sbin/nologin lp:4:/sbin/nologin sync:5:/bin/sync shutdown:6:/sbin/shutdown halt:7:/sbin/halt
显示/etc/fstab文件中每一行的字段数:
[root@localhost ~]# awk ‘{print NF}‘ /etc/fstab 0 1 2 10 1 9 12 1 6 6 6 6
显示netstat.txt文件中每一行的最后一个字段:
[root@localhost ~]# awk ‘{print $NF}‘ netstat.txt State LISTEN LISTEN TIME_WAIT ESTABLISHED ESTABLISHED ESTABLISHED TIME_WAIT TIME_WAIT TIME_WAIT TIME_WAIT
这里需要注意NF和$NF的区别!我们说过,在awk中引用变量不需要带上"$",即便是$1,$2,...也只是awk引用每一行中各个字段的专用符号,这里NF表示当前处理的行的字段数量,$NF则是将NF(即字段数量)的值作为"$#"中的数字"#",从而相当于使用awk的专用符号!
显示netstat.txt文件中每一行的行号:
[root@localhost ~]# awk ‘{print NR}‘ netstat.txt 1 2 3 4 5 6 7 8 9 10 11
如果对一个文件的每一行进行显示的话,则会显示每一行的行号;而在整个文件处理结束之后再进行显示时,则显示的是文件的行号,如下:
[root@localhost ~]# awk ‘END{print NR}‘ netstat.txt 11
显示文件/etc/fstab和/etc/inittab的行数:
[root@localhost ~]# awk ‘{print NR}‘ /etc/fstab /etc/inittab 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
可以发现,这里将两个文本文件当做一个文件来处理了,而如果需要将两个或多个文件分开进行计数时,则可使用内建变量NFR:
[root@localhost ~]# awk ‘{print FNR}‘ /etc/fstab /etc/inittab 1 2 3 4 5 6 7 8 9 10 11 12 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
显示命令行给定的参数个数:
[root@localhost ~]# awk ‘BEGIN{print ARGC}‘ /etc/fstab /etc/inittab 3
在命令行中给定的各个参数会存放在awk的内建数组ARGV中,可使用ARGV[#]引用各个参数:
[root@localhost ~]# awk ‘BEGIN{print ARGV[0]}‘ /etc/fstab /etc/inittab awk [root@localhost ~]# awk ‘BEGIN{print ARGV[1]}‘ /etc/fstab /etc/inittab /etc/fstab [root@localhost ~]# awk ‘BEGIN{print ARGV[2]}‘ /etc/fstab /etc/inittab /etc/inittab
注意:这里第一个参数是awk,而不是‘program‘!
除了内建变量之外,awk还支持用户自定义变量,而自定义变量有两种方式:
(1) 通过选项-v var=value定义变量;
(2) 在program中直接定义;
命令演示:
也可通过选项直接定义:
[root@localhost ~]# awk -v test=‘hello awk‘ ‘{print test}‘ netstat.txt hello awk hello awk hello awk hello awk hello awk hello awk hello awk hello awk hello awk hello awk hello awk
文件有多少行,就显示多少个‘hello awk‘;这里引用变量不需要‘$‘,再次强调!
如果不需要对各行处理,而仅显示一次,则:
[root@localhost ~]# awk -v test=‘hello awk‘ ‘BEGIN{print test}‘ netstat.txt hello awk
在program中定义:
[root@localhost ~]# awk ‘BEGIN{test="hello"; print test}‘ hello
接下来介绍awk常用的Action:
(1) pirnt语句:
格式:
print item1, item2, ...
要点:
①在命令中以逗号作为分隔符,输出时默认以空白字符作为分隔符;
②这里输出的各item可以是字符串、数值、当前记录的字段、变量、数组以及awk的表达式等;
③如果输出的数值,则数值会隐射为字符串后输出,而在计算时仍为数值;
④如果省略item,输出效果相当于‘print $0‘.
示例:
显示/etc/fstab文件中最后4行中每一行的第2、4个字段:
[root@localhost ~]# tail -4 /etc/fstab | awk ‘{print $2,$4}‘ / defaults /boot defaults /home defaults swap defaults
注意:如果在命令行中没有使用逗号分隔,则会被awk认为这是一个字段,并连接起来,如下:
[root@localhost ~]# tail -4 /etc/fstab | awk ‘{print $2 $4}‘ /defaults /bootdefaults /homedefaults swapdefaults
在显示的字段前插入字符串:
[root@localhost ~]# tail -4 /etc/fstab | awk ‘{print "hello:",$2,$4}‘ hello: / defaults hello: /boot defaults hello: /home defaults hello: swap defaults
需要注意的是,只有在引号之外才可以做变量替换,因此在awk中要做变量替换时,不能用引号将之引起来,这里举一个例子:
[root@localhost ~]# tail -4 /etc/fstab | awk ‘{print "hello:",$2}‘ hello: / hello: /boot hello: /home hello: swap [root@localhost ~]# tail -4 /etc/fstab | awk ‘{print "hello:,$2"}‘ hello:,$2 hello:,$2 hello:,$2 hello:,$2
直接显示每一行的内容:
[root@localhost ~]# awk ‘{print}‘ netstat.txt Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN tcp 0 0 10.10.10.140:49808 10.10.10.140:80 TIME_WAIT tcp 0 52 10.10.10.140:22 10.10.10.1:52641 ESTABLISHED tcp 0 0 10.10.10.140:22 10.10.10.1:51926 ESTABLISHED tcp 0 0 10.10.10.140:22 10.10.10.1:52640 ESTABLISHED tcp 0 0 10.10.10.140:49806 10.10.10.140:80 TIME_WAIT tcp 0 0 10.10.10.140:49804 10.10.10.140:80 TIME_WAIT tcp 0 0 10.10.10.140:49810 10.10.10.140:80 TIME_WAIT tcp 0 0 10.10.10.140:49812 10.10.10.140:80 TIME_WAIT [root@localhost ~]#
这里直接给出print相当于‘print $0‘。
[root@localhost ~]# awk ‘{print ""}‘ netstat.txt [root@localhost ~]#
awk会遍历整个文件,文件有多少行,这里就显示多少个空白行。
(2) printf语句:
语法:
printf "FORMAT" item1, item2, ...
功效:
格式化输出:将各个item按位套进"FORMAT"指定的格式中,实现按照一定格式输出的功能。
要点:
①"FORMAT"必须给出;
②不会自动换行,需要显式给出换行控制符"\n";
③"FORMAT"需要分别为后面的每个item指定一个格式化符号(格式符).
格式符:
%c:显示为字符的ASCII码;
%d, %i:显示为十进制整数;
%e, %E:以科学计数法显示;
%f:显示为浮点数;
%g, %G:以科学计数法或浮点形式显示;
%s:显示为字符串;
%u:显示为无符号整数;
%%:显示%自身.
修饰符:
每个格式符还可以有修饰符,可通过在格式符前面加上一些修饰符以控制其格式的显示机制,因此称为修饰符。
常用的修饰符有:
#[.#]:第一个数字#表示显示的宽度,后一个#表示小数点后的精度;
-:以左对齐形式显示;
+:显示为数值的符号.
示例:
格式化输出/etc/passwd文件中的第1列:
[root@localhost ~]# awk -F: ‘{printf "Username: %s\n",$1}‘ /etc/passwd Username: root Username: bin Username: daemon Username: adm Username: lp Username: sync Username: shutdown Username: halt
格式化输出/etc/passwd文件中的第1列和第3列:
[root@localhost ~]# awk -F: ‘{printf "Username: %-18s UID: %d\n",$1,$3}‘ /etc/passwd Username: root UID: 0 Username: bin UID: 1 Username: daemon UID: 2 Username: adm UID: 3 Username: lp UID: 4 Username: sync UID: 5 Username: shutdown UID: 6 Username: halt UID: 7
(3) 操作符
操作符可用于PATTERN或者Action中,操作符主要有以下几种:
①算术操作符
双目:
x+y, x-y, x*y, x/y, x%y, x^y
单目:
-x:取相反数;
+x:将字符串转换为数值;
②字符串操作符
字符串切片:使用gawk内建函数;
没有符号的操作符:表示连接字符串;
③比较操作符
>, >=
<, <=
==, !=
④赋值操作符
+=, -=, *=, /=, ^=, %=
++, --
⑤模式匹配符
~:左侧的字符串是否被右侧的PATTERN匹配;
!~:左侧的字符串是否不被右侧的PATTERN匹配;
⑥逻辑操作符
&&
||
!
⑦函数调用
无参数时:
function_name()
有参数时:
function_name(argu1, argu2, ...)
⑧条件表达式
selector?if-true-expression:if-false-expression
解释:
"selector"是一个条件表达式,如果"selector"为真,则执行"if-true-expression"语句;如果"selector"为假,则执行"if-false-expression"语句。
示例:
显示系统上所有用户的用户名及其说明其用户类型:
[root@localhost ~]# awk -F: ‘{$3>=1000?usertype="Common User":usertype="Sysadmin or Sys User";printf "Username: %-18s Usertype: %s\n",$1,usertype}‘ /etc/passwd Username: root Usertype: Sysadmin or SysUser Username: bin Usertype: Sysadmin or SysUser Username: daemon Usertype: Sysadmin or SysUser Username: adm Usertype: Sysadmin or SysUser Username: lp Usertype: Sysadmin or SysUser Username: sync Usertype: Sysadmin or SysUser Username: shutdown Usertype: Sysadmin or SysUser Username: halt Usertype: Sysadmin or SysUser Username: mail Usertype: Sysadmin or SysUser Username: operator Usertype: Sysadmin or SysUser Username: games Usertype: Sysadmin or SysUser Username: ftp Usertype: Sysadmin or SysUser Username: nobody Usertype: Sysadmin or SysUser Username: systemd-bus-proxy Usertype: Sysadmin or SysUser Username: systemd-network Usertype: Sysadmin or SysUser Username: dbus Usertype: Sysadmin or SysUser Username: polkitd Usertype: Sysadmin or SysUser Username: tss Usertype: Sysadmin or SysUser Username: postfix Usertype: Sysadmin or SysUser Username: sshd Usertype: Sysadmin or SysUser Username: tab Usertype: Common User Username: geoclue Usertype: Sysadmin or SysUser Username: apache Usertype: Sysadmin or SysUser Username: centos Usertype: Common User
操作符同样可使用在PATTERN中,例如,如果系统用户的uid大于1000,则显示其用户名和uid(使用关系表达式):
[root@localhost ~]# awk -F: ‘$3>=1000{printf "Username: %-10s UID: %s\n",$1,$3}‘ /etc/p asswd Username: tab UID: 1000 Username: centos UID: 1001
(4) if-else条件判断
语法格式:
if(condition) {statements}
if(condition) {statements} else {statements}
注意:如果"{statements}"中只有一个语句时,则其花括号‘{}‘可省略;如果"{statements}"中有多个语句时,则其花括号‘{}‘不可省略!以下其他语句类同。
使用场景:
需要对awk取得的整行或某个字段做条件判断时使用。
用法示例:
像刚才的例子,如果系统用户的uid大于1000,则显示其用户名和uid,这里使用if-else语句来实现:
[root@localhost ~]# awk -F: ‘{if($3>=1000) {printf "Username: %-18s Usertype: Common Us er\n",$1} else {printf "Username: %-18s Usertype: Sysadmin or SysUser\n",$1}}‘ /etc/pas swd Username: root Usertype: Sysadmin or SysUser Username: bin Usertype: Sysadmin or SysUser Username: daemon Usertype: Sysadmin or SysUser Username: adm Usertype: Sysadmin or SysUser Username: lp Usertype: Sysadmin or SysUser Username: sync Usertype: Sysadmin or SysUser Username: shutdown Usertype: Sysadmin or SysUser Username: halt Usertype: Sysadmin or SysUser Username: mail Usertype: Sysadmin or SysUser Username: operator Usertype: Sysadmin or SysUser Username: games Usertype: Sysadmin or SysUser Username: ftp Usertype: Sysadmin or SysUser Username: nobody Usertype: Sysadmin or SysUser Username: systemd-bus-proxy Usertype: Sysadmin or SysUser Username: systemd-network Usertype: Sysadmin or SysUser Username: dbus Usertype: Sysadmin or SysUser Username: polkitd Usertype: Sysadmin or SysUser Username: tss Usertype: Sysadmin or SysUser Username: postfix Usertype: Sysadmin or SysUser Username: sshd Usertype: Sysadmin or SysUser Username: tab Usertype: Common User Username: geoclue Usertype: Sysadmin or SysUser Username: apache Usertype: Sysadmin or SysUser Username: centos Usertype: Common User
显示以"/bin/bash"为默认shell的用户的用户名:
[root@localhost ~]# awk -F: ‘{if($NF~/\/bin\/bash$/) print $1}‘ /etc/passwd root tab centos 或者: [root@localhost ~]# awk -F: ‘{if($NF=="/bin/bash") print $1}‘ /etc/passwd root tab centos
这里分别结合了模式匹配符或比较操作符来实现。
以空白字符为分隔符,显示/etc/fstab文件中字段数大于等于10的行:
[root@localhost ~]# awk ‘{if(NF>=10) print}‘ /etc/fstab # Created by anaconda on Sun Feb 19 10:02:11 2017 # See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info 或者: [root@localhost ~]# awk ‘NF>=10‘ /etc/fstab # Created by anaconda on Sun Feb 19 10:02:11 2017 # See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
显示空间使用率大于等于20%的挂载设备:
[root@localhost ~]# df -h Filesystem Size Used Avail Use% Mounted on /dev/mapper/cl-root 50G 9.6G 41G 20% / devtmpfs 982M 0 982M 0% /dev tmpfs 993M 0 993M 0% /dev/shm tmpfs 993M 8.7M 984M 1% /run tmpfs 993M 0 993M 0% /sys/fs/cgroup /dev/sda1 1014M 121M 894M 12% /boot /dev/mapper/cl-home 67G 33M 67G 1% /home /dev/sr0 7.8G 7.8G 0 100% /media/cdrom tmpfs 199M 0 199M 0% /run/user/0 [root@localhost ~]# [root@localhost ~]# df -h | awk -F% ‘{print $1}‘ | awk ‘/\/dev/{if($NF>=20) print $1}‘ /dev/mapper/cl-root /dev/sr0
(5) while循环
语法格式:
while(condition) {statements}
循环条件:
条件为“真”,进入循环;
条件为“假”,退出循环.
使用场景:
①对一行内的多个字段逐一进行类似处理时使用;
②对数组中的各元素逐一处理时使用.
用法示例:
将/etc/grub2.cfg文件中以linux16开头(前面可能有空白符)的行筛选出来,并逐一打印每一行的的各个字段及其每个字段的字符数:
[root@localhost ~]# awk ‘/^[[:space:]]*linux16/{i=1; while(i<=NF) {print $i,length($i); i++}}‘ /etc/grub2.cfg linux16 7 /vmlinuz-3.10.0-514.el7.x86_64 30 root=/dev/mapper/cl-root 24 ro 2 rd.lvm.lv=cl/root 17 rd.lvm.lv=cl/swap 17 rhgb 4 quiet 5 LANG=en_US.UTF-8 16 linux16 7 /vmlinuz-0-rescue-c3d796ed1ad340e09e3d9024cd0350bf 50 root=/dev/mapper/cl-root 24 ro 2 rd.lvm.lv=cl/root 17 rd.lvm.lv=cl/swap 17 rhgb 4 quiet 5
注意:这里调用了awk的内建函数length()。
进一步:仅打印字符数大于7的字段:
[root@localhost ~]# awk ‘/^[[:space:]]*linux16/{i=1; while(i<=NF) {if(length($i)>7) {pr int $i,length($i)}; i++}}‘ /etc/grub2.cfg /vmlinuz-3.10.0-514.el7.x86_64 30 root=/dev/mapper/cl-root 24 rd.lvm.lv=cl/root 17 rd.lvm.lv=cl/swap 17 LANG=en_US.UTF-8 16 /vmlinuz-0-rescue-c3d796ed1ad340e09e3d9024cd0350bf 50 root=/dev/mapper/cl-root 24 rd.lvm.lv=cl/root 17 rd.lvm.lv=cl/swap 17
(6) do-while循环
语法格式:
do {statements} while(condition)
解释:
无论是否符合条件,先执行一次,而后再根据condition进行判断。
使用场景:
至少需要执行一次时使用。
(7) for循环
语法格式:
for(expr1;expr2;expr3) {statements}
详细格式:
for(variable assignment;condition;iteration process) {for-body}
特殊用法:
for(var in array) {for-body}
功效:能够遍历数组中元素的索引;
注意:其中var为变量,array为数组名,{for-body}为循环体。前面提到,awk具有内生循环功能,能自动实现对文本所有行的遍历,而这里的for循环则可实现每一行的字段间的循环。
用法示例:
还是刚才的例子,将/etc/grub2.cfg文件中以linux16开头(前面可能有空白符)的行筛选出来,并逐一打印每一行的的各个字段及其每个字段的字符数:
[root@localhost ~]# awk ‘/^[[:space:]]linux16/{for(i=1;i<=NF;i++) {print $i,length($i)}}‘ /etc/grub2.cfg linux16 7 /vmlinuz-3.10.0-514.el7.x86_64 30 root=/dev/mapper/cl-root 24 ro 2 rd.lvm.lv=cl/root 17 rd.lvm.lv=cl/swap 17 rhgb 4 quiet 5 LANG=en_US.UTF-8 16 linux16 7 /vmlinuz-0-rescue-c3d796ed1ad340e09e3d9024cd0350bf 50 root=/dev/mapper/cl-root 24 ro 2 rd.lvm.lv=cl/root 17 rd.lvm.lv=cl/swap 17 rhgb 4 quiet 5
(8) switch语句
语法格式:
switch(condition) {case VALUE1 or /REGEXP/: statement; case VALUE2 or /REGEXP2/: statement; case VALUE3 or /REGEXP3/: statement; default: statement}
(9) break和continue
功效:
break [n]:跳出n层循环;
continue:结束本轮循环并提前进入下一轮循环;
(10) next
功效:
next:提前结束对本行的处理而直接进入下一行。
next和continue的区别:
continue是用于控制行内字段间的跳转的,而next是用于控制awk的内生循环以实现在行间的跳转,从而提前进入下一行处理。
示例:
显示uid为偶数的用户的用户名和uid
[root@localhost ~]# awk -F: ‘{if($3%2!=0) next; print $1,$3}‘ /etc/passwd root 0 daemon 2 lp 4 shutdown 6 mail 8 games 12 ftp 14 systemd-network 192 polkitd 998 sshd 74 tab 1000 apache 48
(11) 数组(array)
定义数组:
array[index-expression]
注意:array为自定义的数组名,index-expression为索引表达式,awk支持索引数组和关联数组,而在awk中关联数组更为常用。
删除数组:
delete array[index-expression]:删除数组中的某元素;
delete array:删除数组.
要点:
①awk支持关联数组,可使用任何字符串;字符串要使用双引号;
②如果数组中的某个元素事先不存在,在引用时,awk会自动创建这个元素,并将其值初始化为“空串”;而如果引用一个事先不存在的数组元素来做数值运算时,则会自动将该元素赋值为0.
③若要判断数组中是否存在某元素,不可以直接引用,因为一引用就会自动创建(一创建就相当于存在了),而应该使用"index in array"格式来做判断。
④若要遍历整个数组中的元素,则要使用for循环:for(var in array) {for-body}.
用法示例:
创建一个关联数组,数组第一个元素的索引为"mon",值为"Monday",第二个元素的索引为"tue",值为"Tuesday";创建之后显示指定的数组元素:
[root@localhost ~]# awk ‘BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";print weekdays["mon"]}‘ Monday
当不需要对文件进行处理时,可在前面带上"BEGIN"。
进一步:遍历显示数组中的所有元素:
[root@localhost ~]# awk ‘BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";for(i in weekdays) {print weekdays[i]}}‘ Tuesday Monday
使用for的特殊用法去遍历数组中的每个元素时,例如此处,awk会将数组‘weekdays‘中的每个元素的索引赋值给变量i,而不是直接将数组的元素直接赋值给变量i。此处遍历的顺序可能和我们预想的不一致。
判断数组中的某个元素是否存在:
[root@localhost ~]# awk ‘BEGIN{testarray["a"]="hello"; testarray["b"]="hi"; if("a" in t estarray) {print "exist."} else print "not exist." }‘ exist. [root@localhost ~]# [root@localhost ~]# awk ‘BEGIN{testarray["a"]="hello"; testarray["b"]="hi"; if("c" in t estarray) {print "exist."} else print "not exist." }‘ not exist.
根据前面提到,如果我们要引用数组中的某元素,而这个元素事先是不存在的,那么当我们引用时awk会自动创建这个元素,并且初始化为“空串”,当然了,如果我们直接把这个事先不存在的数组元素拿去做运算,则会自动初始化为“0”。在生产环境中,利用这一特性,我们可以实现一个通用而且非常实用的功能:当我们需要统计在文本中某些字符串各自分别出现的次数时,可以直接把这些字符串分别作为一个数组的索引,并且使得在各个要统计的字符串每次出现时,以该字符串作为索引的数组元素自动加1.
说起来有点抽象,接下来我们看一看如何利用这一特性:
用netstat命令查看当前网路连接状态:
[root@localhost ~]# netstat -tan Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN tcp 0 0 10.10.10.140:22 10.10.10.1:52641 ESTABLISHED tcp 0 52 10.10.10.140:22 10.10.10.1:51926 ESTABLISHED tcp 0 0 10.10.10.140:51816 10.10.10.140:80 TIME_WAIT tcp 0 0 10.10.10.140:51818 10.10.10.140:80 TIME_WAIT tcp 0 0 10.10.10.140:22 10.10.10.1:52640 ESTABLISHED tcp 0 0 10.10.10.140:51814 10.10.10.140:80 TIME_WAIT tcp6 0 0 :::80 :::* LISTEN tcp6 0 0 :::22 :::* LISTEN tcp6 0 0 ::1:25 :::* LISTEN tcp6 0 0 10.10.10.140:80 10.10.10.140:49844 TIME_WAIT tcp6 0 0 10.10.10.140:80 10.10.10.140:50304 TIME_WAIT tcp6 0 0 10.10.10.140:80 10.10.10.140:49828 TIME_WAIT
统计netstat命令执行结果中tcp连接的各个状态的个数:
[root@localhost ~]# netstat -tan | awk ‘/tcp\>/{state[$NF]++}END{for(i in state) print i,state[i]}‘ LISTEN 2 ESTABLISHED 3 TIME_WAIT 3
命令解释:
这里首先锚定匹配以‘tcp‘开头的行;接着把匹配到的每一行的最后一个字段(即状态)赋值为数组state的索引,这里因为事先state[$NF]不存在,所以awk会自动创建之,并将对应的数组元素初始化为“空串”;但又因为这里直接将数组元素用于自增运算,因此初始值为0,以用于做自增运算,而我们要统计的各个状态每出现一次,就会自动把这个状态所对应的数组state的索引对应的元素值加1.
最后我们只需要用for循环遍历一次数组state的索引,并显示索引(这里"索引"即为"状态")以及对应的元素值(即各个状态分别出现的次数)。
此外,在生产环境中通常使用ss命令结合awk工具来做统计,这样执行性能更佳。
统计/var/log/httpd/access_log日志文件中各个ip地址从本地Web服务器获取资源的次数:
[root@localhost ~]# awk ‘{ip[$1]++}END{for(i in ip) print i,ip[i]}‘ /var/log/httpd/acce ss_log 10.10.10.138 1014 10.10.10.1 221 10.10.10.139 4024 10.10.10.140 4000
查看/etc/fstab文件内容:
[root@localhost ~]# cat /etc/fstab # # /etc/fstab # Created by anaconda on Tue Oct 4 09:06:12 2016 # # Accessible filesystems, by reference, are maintained under ‘/dev/disk‘ # See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info # UUID=60eb0d1c-9834-4348-9a79-2f91983a8ede / ext4 defaults 1 1 UUID=3995456f-a3e9-4b69-a0de-0fd48068da39 /boot ext4 defaults 1 2 UUID=01d24b59-eee4-472e-9985-ef44fd5e059c swap swap defaults 0 0 tmpfs /dev/shm tmpfs defaults 0 0 devpts /dev/pts devpts gid=5,mode=620 0 0 sysfs /sys sysfs defaults 0 0 proc /proc proc defaults 0 0 /dev/sr0 /media/cd iso9660 defaults 1 2
统计/etc/fstab文件中每个文件系统类型出现的次数:
[root@localhost ~]# awk ‘/^UUID/{fs[$3]++}END{for(i in fs) print i,fs[i]}‘ /etc/fstab swap 1 ext4 2
统计指定文件中每个单词出现的次数,并统计出使用次数最多的英语单词及其出现次数:
[root@localhost ~]# cat test.txt //打开一篇英语文章; I am too busy now. The life is different in the past and now. Let me tell you about my life. In the past,I didn‘t study or do some things. I always played with my parents. My dad often took me to go to the zoo. That was really interesting! But now as a student, I have to stay in school all day. At home, I have too much homework to do.So I have to do homework. Time is flies. I miss past. I hope it back soon. [root@localhost ~]# [root@localhost ~]# [root@localhost ~]# awk ‘{for(i=1;i<=NF;i++) {count[$i]++}}END{for(i in count) {print i,count[i]}}‘ test.txt | sort -r -n -k2 | head -5 I 7 to 5 the 3 have 3 too 2
(12) 函数
awk的函数有两种类型,一种是内置函数,一种是自定义函数。这里我们介绍一下awk的内置函数吧!
数值处理:
rand():返回0和1之间的一个随机数.
实例:
[root@localhost ~]# awk ‘BEGIN{print rand()}‘ 0.237788
注意:只有第一次取得的数是随机的,之后会一直沿用第一次取得的值。
字符串处理:
length():返回指定字符串的长度;
sub(r,s,[t]):以r表示的模式去查找t所表示的字符串中匹配到的内容,并将其第一次匹配到的内容替换为s所表示的内容。
gsub(r,s,[t]):以r表示的模式去查找t所表示的字符串中匹配到的内容,并将其第所有匹配到的内容替换为s所表示的内容。
split(s,a[,r]):以r为分隔符切割字符s,并将其切割后的结果保存至a所表示的数组中。
注意:这里a所表示的数组的索引从1开始,而字段的各个切片分别保存至索引1,2,3,...中。
实例:
统计所有客户端IP地址及其出现的次数:
[root@localhost ~]# netstat -tan Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN tcp 0 52 10.10.10.140:22 10.10.10.1:51134 ESTABLISHED tcp 0 0 10.10.10.140:22 10.10.10.1:51531 TIME_WAIT tcp6 0 0 :::80 :::* LISTEN tcp6 0 0 :::22 :::* LISTEN tcp6 0 0 ::1:25 :::* LISTEN [root@localhost ~]# [root@localhost ~]# netstat -tan | awk ‘/^tcp\>/{split($5,ip,":"); count[ip[1]]++}END{for(i in count) {print i,count[i]}}‘ 10.10.10.1 1 0.0.0.0 2
命令解释:
首先awk会匹配以tcp开头的行,接着split()函数将模式(/^tcp\>/)匹配到的行的第5个字段以":"作为分隔符做切分,各个切片依次保存至ip所表示的数组中;而这里split()函数参数中,ip所表示的数组的索引是从1开始计数的,因此第5个字段":"左侧的ip地址保存至ip[1],":"右侧的端口号保存至ip[2];这里我们需要统计的是ip地址,所以按照前面几个例题的思路,这里使用了数组元素嵌套的方法将ip[1]的值作为数组count的索引。
本文出自 “Tab” 博客,请务必保留此出处http://xuweitao.blog.51cto.com/11761672/1905269
标签:awk用法 编程语言
原文地址:http://xuweitao.blog.51cto.com/11761672/1905269