gawk

0 目录

1 gawk是什么

awk早期在unix上,后由GNU重写为gawk,用于格式化文本输出。因使用习惯仍可使用命令awk,它是gawk的链接。

[root@localhost ~]% ls -l /bin/awk
lrwxrwxrwx. 1 root root 4 Sep  8 13:19 /bin/awk -> gawk

awk可视作一个完整的编程语言,有自己的内建指令等。

2 如何使用awk

2.1 格式

使用格式:gawk [options] 'pattern{action}' FILE,也可处理由管道传递的文本。

其中pattern可理解为“地址定界条件”,用于限定满足特定条件的文本
action为处理文本的语句,多个语句用分号分隔,且这些语句都是awk内的语句,和bash语法不同(更像是C语言的语法)。

2.2 机制

1、awk会对给定文本进行逐行处理;
2、每行默认以空格或tab符作为分隔符1(也可指定其他分隔符),把文件分为各字段;
3、各字段被赋值给变量$1、$2、$3等(类似bash脚本的位置变量),特别地,$0表示当前行的整行内容;

比如,显示/etc/passwd中的各用户名及对应UID:

[root@localhost ~]% tail /etc/passwd
saslauth:x:499:76:Saslauthd user:/var/empty/saslauth:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
ntp:x:38:38::/etc/ntp:/sbin/nologin
avahi-autoipd:x:170:170:Avahi IPv4LL Stack:/var/lib/avahi-autoipd:/sbin/nologin
haldaemon:x:68:68:HAL daemon:/:/sbin/nologin
rtkit:x:498:496:RealtimeKit:/proc:/sbin/nologin
pulse:x:497:495:PulseAudio System Daemon:/var/run/pulse:/sbin/nologin
gdm:x:42:42::/var/lib/gdm:/sbin/nologin

[root@localhost ~]% tail /etc/passwd | awk -F: '{print $1,$3}'			# 以冒号为分隔符,输出第1、3字段
saslauth 499
postfix 89
sshd 74
dbus 81
ntp 38
avahi-autoipd 170
haldaemon 68
rtkit 498
pulse 497
gdm 42

下面的例子更能说明awk是逐行处理数据:

[root@localhost ~]% wc -l /etc/issue						# 文件/etc/issue共3行
3 /etc/issue

[root@localhost ~]% awk '{print "hello"}' /etc/issue		# 动作语句为打印“hello”,与后面的文件无关,仍显示了3次
hello
hello
hello

为避免这种情况,可使用BEGIN、END作为地址定界条件,下述。

2.3 常用option

选项意义
-F用于指定读入文本中以哪个符号作为各字段分隔符。默认是空格或tab。分隔符可指定多个,多个的话就使用“[]”括起来
-v VAR=VALUE用于给变量赋值。虽然在动作语句内也可赋值,但使用该选项,可在动作语句执行前给变量赋值。可使用多次给多个变量赋值。

对于-v选项作用,应该是相当于BEGIN模式下给变量赋值,比如:

[root@localhost ~]% tail /etc/passwd | awk '{FS=":";print $1,$3}'		# 输出/etc/passwd文件后10行的第1、3字段
																	# 第一行分隔符并没有换成指定的“:”,因为按默认分隔符读入文本后,动作语句才开始执行。所以用FS指定的分隔符在第2行后才开始作用。
saslauth:x:499:76:Saslauthd 
postfix 89
sshd 74
dbus 81
ntp 38
avahi-autoipd 170
haldaemon 68
rtkit 498
pulse 497
gdm 42

[root@localhost ~]% tail /etc/passwd | awk -v FS=":" '{print $1,$3}'		# 使用-v选项赋值,则在动作语句执行前就完成赋值了,所以从第1行开始就有效了
saslauth 499
postfix 89
sshd 74
dbus 81
ntp 38
avahi-autoipd 170
haldaemon 68
rtkit 498
pulse 497
gdm 42

当然对于分隔符的指定,使用-F也可。以上仅说明变量在动作语句内赋值,与使用选项赋值的不同2

2.4 常用内置变量

awk包含内置变量用于记录某些特定数据。常用的有:

变量意义
$1、$2等当前行的各字段的内容
$0当前行整行的内容
NR记录正在处理的是文本的第几行
NF记录每行的字段数
FS记录各字段的分隔符
OFS输出内容的各字段的分隔符,默认是空格
FNR参数为多个文件时,每个文件分别列出行数
FILENAME记录处理的文件名

比如,仅输出文件第2行:

[root@localhost ~]% cat /etc/issue
CentOS release 6.8 (Final)
Kernel \r on an \m

[root@localhost ~]% cat /etc/issue | awk 'NR==2{print $0}'			# 使用内置变量NR为2作为地址定界条件,执行动作是输出该行
Kernel \r on an \m

比如,输出每行的最后一个字段:

[root@localhost ~]% cat /etc/issue
CentOS release 6.8 (Final)
Kernel \r on an \m

[root@localhost ~]% cat /etc/issue | awk '{print $NF}'				# NF是每行的总字段数,$NF就是每行的最后一个位置变量
(Final)
\m

比如,参数为多个文件时的行数统计:

[root@localhost ~]% awk '{print NR}' /etc/issue /etc/issue			# 把两个/etc/issue文件视为一个文件,输出了总行数NR。
																	# 注意因为awk是逐行处理文件,所以每行均有输出,且结果就是到当前行为止的总行数。所以效果看上去相当于对每行做了编号
1
2
3
4
5
6
[root@localhost ~]% awk '{print FNR}' /etc/issue /etc/issue			# 使用FNR可分别统计两文件行数
1
2
3
1
2
3

2.5 常用地址定界条件

2.5.1 空

表示处理文件的每一行3

[root@localhost ~]% awk '{print $1}' /etc/issue
CentOS
Kernel

2.5.2 正则表达式

表示仅处理能被指定这则表达式(/PATTERN/)匹配到的行:

[root@localhost ~]% awk '/^CentOS/{print $1,$3}' /etc/issue			# 仅对CentOS开头的行处理
CentOS 6.8

[root@localhost ~]% awk '!/^CentOS/{print $1,$3}' /etc/issue		# 可对指定模式取反
Kernel on

也可使用“/PAT1/,/PAT2/”定界,表示从第一次出现模式PAT1的行到第一次出现模式PAT2的行。
比如,输出/etc/passwd文件中,第一次出现root开头的行到第一次出现daemon开头的行,的第1、3字段:

[root@localhost ~]% head /etc/passwd | awk -F: '/^root/,/^daemon/{print $1,$3}'
root 0
bin 1
daemon 2

注意,模式可以这样使用,但不支持行号的定界,如“3,5”无意义,不是第3行到第5行的意思。

2.5.3 关系表达式

可使用关系表达式来限定要处理的行,值为“真”则处理,“假”不处理。
关系运算符还是那些:==、!=、>、<、、!4等等。

比如,显示指定文件3到5行的内容:

[root@localhost ~]% head -5 /etc/passwd					# 文件的前5行
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

[root@localhost ~]% awk 'NR>=3&&NR<=5{print $0}' /etc/passwd		# 文件的3到5行
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

注意,虽然支持“/PAT1/,/PAT2/”这样来定界,但不支持“3,5”来表示第3行到第5行。所以要使用内置变量NR来辅助。

比如,输出UID小于3的用户信息:

[root@localhost ~]% awk -F: '$3<3{print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

2.5.4 BEGIN和END

BEGIN模式,表示对指定文件、文本处理前,执行1次BEGIN后定义的执行语句;
END模式,表示对指定文件、文本处理后,执行1次END后定义的执行语句。

比如,输出一个文件的文件名:

[root@localhost ~]% awk '{print "hello"}' /etc/issue			# /etc/issue文件有3行,因为awk是逐行处理,所以hello被显示了3次
hello
hello
hello
[root@localhost ~]% awk 'BEGIN{print "hello"}' /etc/issue		# BEGIN和END模式后的语句都只会执行1次
hello
[root@localhost ~]% awk 'END{print "hello"}' /etc/issue
hello

上例只是说明BEGIN和END语句都只会执行1次。
实际在使用过程中,因为BEGIN的动作语句是在对指定文本处理前,所以它的显示结果往往可作为表头,使输出文本更明了美观;相应地,END的动作语句可在awk命令执行完成后,用于总结结果。

比如,显示UID小于3的用户的用户名和UID,利用BEGIN先显示表头:

[root@localhost ~]% awk -F: 'BEGIN{print "UserName UID"} $3<3{printf "%-9s %s\n",$1,$3}' /etc/passwd
UserName UID
root      0
bin       1
daemon    2

接上例,在动作语句输出结果后,利用END把各UID的和输出:

[root@localhost ~]% awk -F: 'BEGIN{print "UserName UID";sum=0} $3<3{printf "%-9s %s\n",$1,$3;sum+=$3} END{printf "The sum of UID:%d\n",sum}' /etc/passwd		# 注意,sum的初始赋值为0,如果写在动作语句中,则每次执行动作语句都会被初始赋值,无法达到预期效果。所以初始赋值要在BEGIN的动作语句中或使用选项“-v”赋值
UserName UID
root      0
bin       1
daemon    2
The sum of UID:3

[root@localhost ~]% awk -F: 'BEGIN{print "UserName UID";sum=0} $3<3{printf "%-9s %s\n",$1,$3;sum+=$3;printf "The sum of UID:%d\n",sum}' /etc/passwd			# 如果不使用END,而把输出sum这条指令放在awk动作语句中,则awk逐行处理,每次都会执行。所以END作为“总结”式的输出才能达到预期效果
UserName UID
root      0
The sum of UID:0
bin       1
The sum of UID:1
daemon    2
The sum of UID:3

2.6 常用动作语句

awk的动作语句不同于bash的语法,而是类似C语言5

2.6.1 常用内置函数和内建命令

awk内可自定义函数,也有很多内置函数和内建命令,常用的有:

函数作用
print输出指定字符串或字段。不同字段使用“,”隔开,否则即便有空格仍会连在一起作为一个字符串输出
printf格式化输出,用法同C语言的printf
length(STRING)返回字符串STRING的长度
spit(s,a[,r])对字符串s,以r做分隔符,把各字段存放在数组a中(该数组以1开始编号而不是0)
exit退出当前的awk

这些使用很明了,不再举例赘述。

2.6.2 控制语句

2.6.2.1 选择分支

使用关键字if,或三目运算符a?b:c。多路分支可使用switch和case。

比如,输出UID小于3的用户名和UID:

[root@localhost ~]% awk -F: '{if($3<3) {print $1,$3}}' /etc/passwd
root 0
bin 1
daemon 2

比如,使用三目运算符判断是系统用户或是普通用户:

[root@localhost ~]% tail -4 /etc/passwd | awk -F: '{a=$3>=500?"Common User":"Root or System User";print $1,a}'
rtkit Root or System User
pulse Root or System User
gdm Root or System User
user1 Common User

多路分支不赘述了。

2.6.2.2 循环语句与数组

虽然awk在处理时是逐行处理,已经是循环了,但有时需要对每一行的各字段进行相同处理。这就需要循环语句控制,很多时候还需要数组6

比如,统计给定的文本中,每行的各字段的字符数:

[root@localhost ~]% cat /etc/issue
CentOS release 6.8 (Final)
Kernel \r on an \m

[root@localhost ~]% awk '{for(i=1;i<=NF;i++) {len=length($i);printf "%s,%d\n",$i,len}}' /etc/issue
CentOS,6
release,7
6.8,3
(Final),7
Kernel,6
\r,2
on,2
an,2
\m,2

特别地,可使用for循环的特定格式for(i in ARRAY) {...},遍历数组下标,从而遍历数组(遍历次序不定):

[root@localhost ~]% awk 'BEGIN{day["mon"]="Monday";day["tue"]="Tuesday";day["wen"]="Wensday";for(i in day) {print day[i]}}'
Wensday
Monday
Tuesday

比如,统计指定文件内,各单词(没有包含空格的字符串,可以有数字)出现的次数:

[root@localhost ~]% cat /etc/issue
CentOS release 6.8 (Final)
Kernel \r on an \m

[root@localhost ~]% awk '{for(i=1;i<=NF;i++) {word[$i]++}} END{for(i in word) {print i,word[i]}}' /etc/issue
on 1
6.8 1
CentOS 1
Kernel 1
\m 1
(Final) 1
release 1
\r 1
an 1

# 因为不同文件单词数不一样,单词也不同。故这里利用各单词,作为关联数组的下标。数组各元素的值初始都是0,所以只要遇见某单词,则该单词对应下标的数组元素加1即可

7

统计当前主机网络连接的所有状态和各状态的个数:

[root@localhost ~]% netstat -tanlp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address               Foreign Address             State       PID/Program name   
tcp        0      0 0.0.0.0:22                  0.0.0.0:*                   LISTEN      1574/sshd           
tcp        0      0 127.0.0.1:25                0.0.0.0:*                   LISTEN      1653/master         
tcp        0      0 127.0.0.1:6010              0.0.0.0:*                   LISTEN      1691/sshd           
tcp        0     64 192.168.0.105:22            192.168.0.104:50283         ESTABLISHED 1691/sshd           
tcp        0      0 :::22                       :::*                        LISTEN      1574/sshd           
tcp        0      0 ::1:25                      :::*                        LISTEN      1653/master         
tcp        0      0 ::1:6010                    :::*                        LISTEN      1691/sshd

[root@localhost ~]% netstat -tanlp | awk 'NR>=3{print $6}'			# 去掉前2行,取第6字段就是所有连接的状态
LISTEN
LISTEN
LISTEN
ESTABLISHED
LISTEN
LISTEN
LISTEN

[root@localhost ~]% netstat -tanlp | awk 'NR>=3{State[$6]++} END{for(i in State) {printf "The number of state %s is %d.\n" ,i,State[i]}}'			# 统计状态数并输出
The number of state ESTABLISHED is 1.
The number of state LISTEN is 6.

while、do-while、break、continue语法和作用不再赘述。

关键字next,可使awk结束当前行的处理,直接处理下一行。类似于continue的作用,但continue是作用于1行内的各字段的循环;next是针对awk自带的对每行的循环的。

(完)


  1. 分隔符不论一个或是连续的若干个(比如多个连续的空格符),都会被视为一个分隔符。
    这样就比cut命令好用些,cut命令切割文本只能指定固定长度的分隔符。 ↩︎

  2. 这里也说明,awk内置变量的赋值会先于用户在awk动作语句中的定义变量的赋值。 ↩︎

  3. 注意,地址定界条件为空时,要给定awk要处理的文本或文件,否则会卡住不动了…… ↩︎

  4. ~和!~表示能否被指定模式匹配、不匹配,和bash的“=~”意义相同 ↩︎

  5. 包括算术运算符、逻辑运算符(包括三目运算符a?b:c)、函数的定义和引用、各控制语句关键字等都类似C语言。此处不赘述了 ↩︎

  6. awk支持关联数组,且无需先定义后使用。关联数组类似Python中的字典。 ↩︎

  7. 因为关联数组可不声明就直接引用(初始值为数值0和字符串空),所以如需判断一个数组元素是否存在时,不能判断它是否为空。因为只要一判断就相当于引用了(先引用才能判断)。所以应使用“INDEX in ARRAY”,通过查看索引是否存在于该数组来判断。 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值