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内可自定义函数,也有很多内置函数和内建命令,常用的有:
函数 | 作用 |
---|---|
输出指定字符串或字段。不同字段使用“,”隔开,否则即便有空格仍会连在一起作为一个字符串输出 | |
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即可
统计当前主机网络连接的所有状态和各状态的个数:
[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自带的对每行的循环的。
(完)
分隔符不论一个或是连续的若干个(比如多个连续的空格符),都会被视为一个分隔符。
这样就比cut命令好用些,cut命令切割文本只能指定固定长度的分隔符。 ↩︎这里也说明,awk内置变量的赋值会先于用户在awk动作语句中的定义变量的赋值。 ↩︎
注意,地址定界条件为空时,要给定awk要处理的文本或文件,否则会卡住不动了…… ↩︎
~和!~表示能否被指定模式匹配、不匹配,和bash的“=~”意义相同 ↩︎
包括算术运算符、逻辑运算符(包括三目运算符a?b:c)、函数的定义和引用、各控制语句关键字等都类似C语言。此处不赘述了 ↩︎
awk支持关联数组,且无需先定义后使用。关联数组类似Python中的字典。 ↩︎
因为关联数组可不声明就直接引用(初始值为数值0和字符串空),所以如需判断一个数组元素是否存在时,不能判断它是否为空。因为只要一判断就相当于引用了(先引用才能判断)。所以应使用“INDEX in ARRAY”,通过查看索引是否存在于该数组来判断。 ↩︎