一、基础概念

awk命令是Linux文本处理三剑客之一,其man文档把功能概况为pattern scanning and processing language(模式扫描机处理语言),通俗来讲awk命令是一个文本报告生成器,用于实现格式化文本输出,在处理文本文件时对文档中的某字段按条件执行操作,作者为aho、weinberger、kernighan,命令名称由其名字的首字母组成。awk命令大概出现在1970年左右的unix系统中,后来在开源领域不得不把awk所有的功能在Linux中重新实现,现在Linux用到的awk其实是gawk命令

二、命令用法

命令格式:gawk [option] 'program' File…

1. option

  • -F:指明输入字段时的分隔符
  • -v var=value:自定义变量,变量名区分大小写
    内建变量:
    FS:input field separator,输入时的字段分隔符,默认为空白字符
    OFS:out field separator,输出时字段的分隔符,默认为空白
    RS:input record separator,输入时的换行符
    ORS:output record separator,输出时的换行符
    NF:number of field,每一行的字段数量
    NR:number of record,文件中的行数
    FNR:file number of record,对多个文件分别计数
    FILENAME:显示文件名
    ARGC:命令行中参数个数
    ARGV[0]:命令行中所给定的各参数,需要指定下标[#]

2. program

programpatter(模式)及用一对花括号包括的ACTION STATEMENTS(动作语句)组成

(1)PATTER

在awk里是类似地址定界,可在patter前加!表示取反之意

  • empty:不定义patter,处理文本文件的每一行

  • BEGIN{}:仅在开始处理文件中的文本之前执行一次

  • END{}:仅在文本处理完成之后,命令之前执行一次

~]# awk -F: 'BEGIN{print "username  uid"}{printf "%-10s %-4d\n",$1,$3}END{print "=============="}' /etc/passwd
username uid
root 0
bin 1
daemon 2
adm 3
lp 4
sync 5
shutdown 6
halt 7
mail 8
==============
[root@node1 source]#
  • /regular expression/:写明正则表达式,仅处理匹配行
    显示fstab中以uuid开头的行:awk '/^UUID/{print}' /etc/fstab
  • relational expression:关系表达式,结果为真的才被处理,非0为真,非空串为真
    显示id号大于100的用户:awk -F: '$3>100{print $1,$3}' /etc/passwd
  • /pat/,/pat/:指明两个关键字,匹配之间的内容

(2)ACTION STATEMENTS

动作语句,语句中用分号分割

① print命令

print item,item2...:item之间用逗号隔开,item可以是字符串、数值、当前记录的字段、变量或awk表达式,省略item,相当于打印$0

② printf命令

printf "FORMAT",item1,item2:完成格式化输出的命令,通过FORMAT设定格式,然后把每一个item对位放在FORMAT中的每个格式符的位置。FORMAT必须要给出,其输出信息不会自动换行,需要在FORMAT中显示给出换行控制符\n才能完成换行

格式符:
%c:显示字符的ASCII码
%d、%i:显示十进制整数
%e、%E:科学技术法数值显示
%f:显示为浮点数
%g、%G:以科学计数法或浮点形式数值显示
%s:显示字符串
%u:无符号整数
%%:显示%自身

修饰符:%#[.#]
%3.1f:第一个数字控制显示宽度,第二个#表示小数点后精度
%-:左对齐
%+:显示数值的符号
  • 示例一:显示/etc/passwd文件中的用户名称
[root@192 sh]# awk -F: '{printf "Username: %s\n",$1}' /etc/passwd
Username: root
Username: bin
Username: daemon
Username: adm
Username: lp
.......

给定printf命令,用一组双引号将FORMAT包含,FORMAT中使用格式符%s为后面的变量进行占位并控制格式,处理时把$1的值替换到%s处,最后在手动指定\n换行,FORMAT和item之间使用逗号分割

  • 示例二:显示/etc/passwd文件中的用户名称和ID号
[root@192 sh]# awk -F: '{printf "Username: %s  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
.......

同示例一,只不过示例二给定两个格式符%s和%d,awk会自动将两个item对位替换至格式符处

  • 示例三:显示/etc/passwd文件中的用户名称和ID号,加入修饰符
[root@192 sh]# awk -F: '{printf "Username: %10s  UID: %4d\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

示例三使用格式符+修饰符%10s和%4d,对输出信息进行宽度控制,只不过对其方式为右对齐

  • 示例四:显示/etc/passwd文件中的用户名称和ID号,加入修饰符并调整为左对齐
[root@192 sh]# awk -F: '{printf "Username: %-10s  UID: %-4d\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

③ expressions:条件表达式

格式为:selector?if-true:if-false

操作符:
算数运算操作符: x+y、x-y、x*y、x/y、x%y、-x、+y
赋值操作符: =、+=、-=、/=、%=、^=、++、--
数值比较操作符:>、>=、<、<=、!=、==
模式匹配符:~、!~
逻辑操作符:&&、||、!
定义的变量要加双引号
  • 示例一:判断用户的id是否大于1000,如果大于则显示user,否则显示sys
    awk -F: '{$3>=1000?type="user":type="sys";printf "username: %-10s UID: %-5d type: %-7s\n",$1,$3,type}' /etc/passwd
username: root                  UID: 0      type: sys      
username: nobody UID: 65534 type: user
username: sssd UID: 992 type: sys
username: zhangsan UID: 1000 type: user

通过对第三个字段的数值进行判断,如果大于1000则复制type变量为user,小于则赋值type变量为sys,变量赋值时一定要加上双引号

④ control(控制语句)

if语句用法
  • 单分支:if(condition) statements,单分支语句不用加花括号
    condition:条件
    statements:条件为真的语句
示例一:显示uid大于10的用户

]# awk -F: '{if($3>10) print $1,$3}' /etc/passwd
operator 11
games 12
ftp 14
nobody 65534
dbus 81
.......
示例二:如果用户的默认shell为bash,则显示其用户名及其shell

~]# awk -F: '{if($NF=="/bin/bash") print $1,$NF}' /etc/passwd
root /bin/bash
zhangsan /bin/bash
lisi /bin/bash
示例三:如果某行字段大于5段,则显示之
~]# awk '{if(NF>5) printf "%-95s %-2d\n",$0,NF}' /etc/fstab
# Created by anaconda on Tue Apr 20 03:57:24 2021 10
# Accessible filesystems, by reference, are maintained under '/dev/disk/'. 9
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info. 12
# After editing this file, run 'systemctl daemon-reload' to update systemd 11
# units generated from this file. 6
UUID=c077882c-9433-4d90-ba75-79c137a956b7 / xfs defaults 0 0 6
  • 双分支:if(condition) {statements} else {statements}
    else后跟条件为假的语句,注意加话括号
示例二:显示uid大于1000则输出user,小于则输出sys
[root@node1 source]# awk -F: '{if($3>1000) {printf "%-10s %-4s\n",$1,"user"} else {printf "%-10s %-4s\n",$1,"sys"}}' /etc/passwd
root sys
bin sys
daemon sys
adm sys
lisi user
while循环

while循环格式:var;while(condition) {statements}
第一次判断时如果为假则一次都不循环,一般使用在对一行内的多个字段逐一进行处理时使用,也可对数组中的元素处理时。循环前先定义变量i,用分号分割i的修正语句

示例一:遍历文件的每个字段,对每个字段做长度统计
~]# awk '/[[:space:]]*search/{i=1;while(i<=NF) {print $i,length($i); i++}}' /etc/grub2.cfg
if 2
[ 1
x$feature_platform_search_hint 30
= 1
xy 2
]; 2
then 4
search 6
--no-floppy 11
--fs-uuid 9
--set=root 10
--hint-bios=hd0,msdos1 22
--hint-efi=hd0,msdos1 21
--hint-baremetal=ahci0,msdos1 29
--hint='hd0,msdos1' 19
7b9dd426-d07f-49cb-b500-5f04fa478bb2 36

首先使用patter搜索包含search字符串的行,接着定义变量i,当i的数值小于本行的长度NF的数值时,打印当前i的内容及用内建函数length()统计的数值,最后做变量i数值修正

do while循环

格式:do {statements} while(condition)
不管条件为真为假,都会先执行一遍循环体

for循环

for:可以遍历数组元素。
格式:for(变量赋值;条件判断;变量修正) statements

 示例一:遍历文件的每个字段,对每个字段做长度统计
~]# awk '/[[:space:]]*search/{for(i=1;i<=NF;i++) print $i,length($i)}' /etc/grub2.cfg
if 2
[ 1
x$feature_platform_search_hint 30
= 1
xy 2
]; 2
then 4
search 6
--no-floppy 11
--fs-uuid 9
--set=root 10
--hint-bios=hd0,msdos1 22
--hint-efi=hd0,msdos1 21
--hint-baremetal=ahci0,msdos1 29
--hint='hd0,msdos1' 19
7b9dd426-d07f-49cb-b500-5f04fa478bb2 36
示例二:同示例一题目,只不过显示长度大于20的字段
~]# awk '/[[:space:]]*search/{for(i=1;i<=NF;i++) {if(length($i)>=20) print $i,length($i)}}' /etc/grub2.cfg
x$feature_platform_search_hint 30
--hint-bios=hd0,msdos1 22
--hint-efi=hd0,msdos1 21
--hint-baremetal=ahci0,msdos1 29
7b9dd426-d07f-49cb-b500-5f04fa478bb2 36
7b9dd426-d07f-49cb-b500-5f04fa478bb2 36
x$feature_platform_search_hint 30
--hint-bios=hd0,msdos1 22
--hint-efi=hd0,msdos1 21
--hint-baremetal=ahci0,msdos1 29
7b9dd426-d07f-49cb-b500-5f04fa478bb2 36
7b9dd426-d07f-49cb-b500-5f04fa478bb2 36

示例二中嵌套了一个if语句,需要注意嵌套的if语句要包含在花括号内

  • for循环的特殊用法:for(var in array) {statements}:此种方式会把数组array的下标赋值给变量var,接着就可以遍历数组中的元素了
switch语句

语法格式:switch (expression) {case value1 or /regexp/: statement; case value2 or /regexp/;default:statement}

循环控制控制符
  • break[#]:退出n层循环
  • continue:提前结束这轮循环,进入下一轮
  • next:awk中的特殊语句,提前结束awk对本行的处理,而进入下一行
  • exit:退出

三、awk中的数组

关联数组格式:array[index]

1. index的类型

可以有以下几种常见的种类

  • 普通索引:可使用任意字符串,字符串要加双引号
  • 为声明索引:如果某索引元素实现不存在,在引用时awk会自动创建此元素,并将其初始为空,如果做数值计算,这个元素就会被当成0使用。需要注意如果想知道某个数组是否存在某元素,要使用index in array 格式进行

2. 定义数组

定义格式:array["index_expression"]=""

  • ~]# awk 'BEGIN{day["1"]="monday";day["2"]="tuesday";print day["1"]}'
    monday
  • ~]# awk 'BEGIN{day["1"]="monday";day["2"]="tuesday";print day["2"]}'
    tuesday

3. 遍历数组

遍历数组中的元素需要使用for循环时,变量i会替换成数组的下标

[root@node1 ~]# awk 'BEGIN{day["1"]="monday";day["2"]="tuesday";for(i in day) {print day[i]}}'
monday
tuesday
示例一:使用awk统计链接的状态
[root@centos6 ~]# 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 64 192.168.1.250:22 192.168.1.208:13455 ESTABLISHED
tcp 0 0 192.168.1.250:22 192.168.1.208:12766 ESTABLISHED
tcp 0 0 :::22 :::* LISTEN
[root@centos6 ~]# netstat -tan | awk '/^tcp/{state[$NF]++} END{for(i in state) {print i,state[i]}}'
ESTABLISHED 2
LISTEN 2
示例二:遍历nginx日志,查看访问次数大于10的ip地址
[root@node1 nginx]# awk '{ip[$1]++}END{for(i in ip) {if(ip[i]>10) {printf "%-20s count: %-4s\n",i,ip[i]}}}' bak | sort -nk3
40.77.167.105 count: 11
144.91.106.14 count: 14
34.211.221.72 count: 15
168.119.249.94 count: 16
34.221.89.26 count: 17
45.95.53.153 count: 17
52.225.230.5 count: 18
8.214.35.220 count: 18
103.97.201.132 count: 21
111.196.124.162 count: 23
119.8.177.149 count: 31
61.49.79.64 count: 45
117.74.135.35 count: 980

四、awk中的函数

做数值处理

  • rand():返回0和1之间的随机小数

字符串处理

  • length([s]):返回指定字符串的长度
  • sub(r,s,[t]):以r所表示的模式,查找t所表示的字符串,并将其第一次出现替换为s所表示的内容
  • gsub(r,s,[t]):以r所表示的模式,查找t所表示的字符串,并将其所有替换为s所表示的内容
  • split(s,a[,r]):以r为分割符切割字符s,并将其切割后的结果保存至a所表示的数组中,数组编号从1开始

五、练习

  1. 统计/etc/fstab文件中,每个文件系统的个数
    awk '!/^#|^$/{filetype[$3]++}END{for(i in filetype)print i,filetype[i]}' /etc/fstab
    awk '/^UUID/{type[$3]++}END{for(i in type) print i,type[i]}' /tmp/fstab
  2. 统计指定文件中,每个单词出现的个数;
    awk '{for(i=1;i<=NF;i++) word[$i]++}END{for(i in word) print i,word[i]}' /etc/fstab
  3. 如果用户的id号大于1000,就输出系统用户,小于则显示普通用户
    awk -F: '{if($3>1000){printf "Username: %-15s Common User\n",$1} else {printf "Username: %-15s Admin user\n",$1}}' /etc/passwd
  4. 如果用户shell为/bin/bash,则显示用户名与bash
    awk -F: '$NF=="/bin/bash"{printf "Username: %-15s %10s\n",$1,$7}' /etc/passwd
    awk -F: '{if($NF~"/bin/bash") {printf "Username: %-15s %10s\n",$1,$NF}}' /etc/passwd
  5. 如果某一行字段大于5个,就显示整个字段,否则不显示
    awk -F: '{if(NF>5) {print}}' /etc/passwd
  6. df -h显示的设备空间空间大于10则显示
    df -h | awk -F% '{print $1}' | awk 'BEGIN{print "===========devuse==========="}/^\//{if($NF>10) {printf "devname: %-5s use: %d%%\n",$1,$NF }}'
  7. 打印每个字段以及字段的长度
    awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {print $i,length($i);i++}}' /etc/grub2.cfg
    awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {print $i,length($i)}}' /etc/grub2.cfg
  8. 打印每个字段以及字段的长度,仅显示大于等于7的
    awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {if(length($i)>=7) {print $i,length($i)}i++}}' /etc/grub2.cfg
    awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {if(length($i)>=7) {print $i,length($i)}}}' /etc/grub2.cfg
  9. 显示用户id号为偶数的用户
    awk -F: '{if($3%2!=0)next;print $1,$3}' /etc/passwd
  10. ss查看状态,取出各状态名以及出现次数
    ss -tan | awk '!/^State/{sstype[$1]++}END{for(i in sstype){print i,sstype[i]}}'
  11. 查看/var/log/httpd/access_log,每个ip对服务器请求
    awk '{ip[$1]++}END{for(i in ip){print i,ip[i]}}' /var/log/httpd/access_log