1. 什么是awk
**awk**是一个报告生成器,可以格式化文本输出。
通常我们在Linux下使用的awk,其实是gawkawk默认以空格作为分隔符号,整个字段使用$0来代替,其子字段使用$1、$2等等,以此类推
实战演示
[root@localhost ~]# which awk
/bin/awk
[root@localhost ~]# ll /bin/awk
lrwxrwxrwx. 1 root root 4 5月 7 21:04 /bin/awk -> gawk
# 如果不添加这里的逗号(,)则没有下面的空格隔开
[root@localhost ~]# tail -5 /etc/fstab | awk '{print $2, $3}'
swap swap
/dev/shm tmpfs
/dev/pts devpts
/sys sysfs
/proc proc
2. 使用格式
语法
- awk [options] 'program' FILE ... - program: PATTERN{ACTION STATEMENTS} - PATTERN表示匹配模式 - {ACTION STATEMENTS}表示动作语句 - ACTION可以有多个语句,使用分号分隔 - 这里的program类似于 Bash 中命令,如print、printf - 选项: - -F:指明输入时用到的字段分隔符 - 默认使用空格作为分隔符,这里的空格不限制长度 - 如果是但分隔符,不需要指定 - [%' ']表示%和空格作为分隔符 - -v var=value: 自定义变量
3. 命令详解
3.1print命令
格式
- print item1, item2, ...
要点
- (1) 逗号分隔符,默认输出空格作为分隔符
- (2) 输出的各item可以字符串、数值、当前记录的字段、变量或awk的表达式
- (3) 如省略item,相当于print $0,打印整行字符
- (4) 在awk中变量替换需要在引号之外才能生效
实战演示
[root@localhost ~]# tail -5 /etc/fstab | awk '{print "Hello:",$2,$3,6}'
Hello: swap swap 6
Hello: /dev/shm tmpfs 6
Hello: /dev/pts devpts 6
Hello: /sys sysfs 6
Hello: /proc proc 6
[root@localhost ~]# tail -5 /etc/fstab | awk '{print "Hello:"$2}'
Hello:swap
Hello:/dev/shm
Hello:/dev/pts
Hello:/sys
Hello:/proc
[root@localhost ~]# tail -5 /etc/fstab | awk '{print "Hello:$2"}'
Hello:$2
Hello:$2
Hello:$2
Hello:$2
Hello:$2
# 如省略`item`,相当于`print $0`,打印整行字符
[root@localhost ~]# tail -5 /etc/fstab | awk '{print}'
/dev/mapper/VolGroup-lv_swap 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
# 这里打印出来5行空白,是因为awk本身就是遍历整个文本的
[root@localhost ~]# tail -5 /etc/fstab | awk '{print ""}'
3.2 变量
内建变量
- FS - input field seperator,指定输入时的分隔符,默认为空白字符
- OFS - output field seperator,指定输出时的分隔符,默认为空白字符
- RS - input record seperator,输入时的换行符,默认为换行符
- ORS - output record seperator,输出时的换行符,默认为换行符
- NF - number of field,每一行的字段数量
- NR - number of record,文件中的行数
- FNR - 各文件分别计数行数
- FILENAME - 显示当前文件名
- ARGC - 命令行参数的个数
- ARGV - 是一个数组,保存的是命令行所给定的各参数
自定义变量
- (1) -v var=value - 变量名区分字符大小写
- (2) 在program中直接定义变量 - 变量用到的时候直接定义
实战演示
# 指明输出分隔符为":"
[root@localhost ~]# awk -v FS=':' '{ print $1 }' /etc/passwd
root
bin
daemon
[root@localhost ~]# awk -F: '{ print $1 }' /etc/passwd
root
bin
daemon
# 指定输出时的分隔符为":",默认的为空格
[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
...
# 这里表示再输入的时候将空格也看作为换行符,把默认的原有换行符也看成换行符
[root@localhost ~]# awk -v RS=' ' '{ print }' /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
...
# 注意在引用变量的时候不需要$,字段中需要$
[root@localhost ~]# awk '{ print NF }' /etc/fstab
0
1
2
10
...
# 加上$,相当于打印对应数值的字段,以空格作为分隔符
[root@localhost ~]# awk '{ print $NF }' /etc/fstab
#
/etc/fstab
2016
...
# 这里打印出行号
[root@localhost ~]# awk '{ print NR }' /etc/fstab
1
2
3
...
# 当跟上多个文件的时候,就表示多个文件的共同计数
[root@localhost ~]# awk '{ print NR }' /etc/fstab /etc/issue
1
2
3
...
# 多个文件,单独计数
[root@localhost ~]# awk '{ print FNR }' /etc/issue /etc/fstab
1
2
3
1
2
...
# 输出多行,这就是awk的循环功能了
[root@localhost ~]# awk '{ print FILENAME }' /etc/issue
/etc/issue
/etc/issue
/etc/issue
# 这里使用BEGIN,可以防止因为awk的特性的多次出现情况
# 第一个参数是awk,第二个参数是/etc/issue
[root@localhost ~]# awk 'BEGIN{ print ARGC }' /etc/issue
2
[root@localhost ~]# awk 'BEGIN{ print ARGV[0] }' /etc/issue
awk
[root@localhost ~]# awk 'BEGIN{ print ARGV[1] }' /etc/issue
/etc/issue
# 这里/etc/issue的意思就是表示打印的行数,如果只想打印一行的话,使用BEGIN
[root@localhost ~]# awk -v test='Hello awk' 'BEGIN{ print test }' /etc/issue
Hello awk
[root@localhost ~]# awk -v test='Hello awk' '{ print test }' /etc/issue
Hello awk
Hello awk
Hello awk
# 在`program`中直接定义变量
[root@localhost ~]# awk 'BEGIN{ test="Hello awk"; print test }'
Hello awk
3.3printf命令
格式化输出
- 格式printf FORMAT, item1, item2, ...
- 注意(1) FORMAT必须给出(2) 不会自动换行,需要显式给出换行控制符,\n(3) FORMAT中需要分别为后面的每个item指定一个格式化符号
格式符
- %c: 显示字符的ASCII码
- %d, %i: 显示十进制整数
- %e, %E: 科学计数法数值显示
- %f:显示为浮点数
- %g, %G:以科学计数法或浮点形式显示数;
- %s:显示字符串
- %u:无符号整数
- %%: 显示%自身
修饰符
- #[.#] - 第一个#表示控制显示的宽度 - 第二个#表示小数点后的精度,如果不是小数可以省略 - -:表示左对齐,默认为右对齐 - +:显示数值的符号 - 如%3.1f - 如%3d
实战演示
# 默认需要引号引起来,并且没有换行符
[root@localhost ~]# awk -F: '{ printf "%s",$1 }' /etc/passwd
rootbindaemonadmlpsyncshutdownhaltmailuucpoperatorgamesgopherftpnobodydbususbmuxdvcsarpcrtkitavahi-autoipdabrtrpcusernfsnobodyhaldaemongdmntpapachesaslauthpostfixpulsesshdtcpdumpescapenamedmysql
# 换行符指定
[root@localhost ~]# awk -F: '{ printf "%s\n",$1 }' /etc/passwd
root
bin
daemon
...
# 定义输出
[root@localhost ~]# awk -F: '{ printf "Username:%s\n",$1 }' /etc/passwd
Username:root
Username:bin
Username:daemon
...
# 定义输出
[root@localhost ~]# awk -F: '{ printf "Username:%s, UID:%d\n", $1,$3 }' /etc/passwd
Username:root, UID:0
Username:bin, UID:1
Username:daemon, UID:2
...
# 格式化输出
[root@localhost ~]# awk -F: '{ printf "Username:%-10s, UID:%5d\n", $1,$3 }' /etc/passwd
Username:root , UID: 0
Username:bin , UID: 1
Username:daemon , UID: 2
3.4 操作符
算术操作符
- x+y
- x-y
- x*y
- x/y
- x^y
- x%y
- -x:表示负数
- +x:把一个字符串转换为数值
字符串操作符
- 没有符号的操作符,字符串连接
赋值操作符
- =
- +=
- -=
- *=
- /=
- %=
- ^=
- ++:自加
- --:自减
比较操作符
- >
- >=
- <
- <=
- !=
- ==
模式匹配符
- ~:做成的字符串是否能被右侧的模式所匹配
- !~:是否不匹配
逻辑操作符
- &&
- ||
- !
函数调用
- function_name(argu1, argu2, ...)
条件表达式
- selector ? if-true-expression : if-false-expression - 三目运算符,selector表示表达式 - 如果为真执行if-true-expression,如果为假执行if-false-expression
实战演示
# 条件表达式
[root@localhost ~]# awk -F: '{ $3>=1000?usertype="Common User":usertype="Sysadmin or SysUser"; printf "%15s:%-s\n", $1,usertype }' /etc/passwd
root:Sysadmin or SysUser
bin:Sysadmin or SysUser
daemon:Sysadmin or SysUser
...
3.5PATTERN匹配
类似于正则表达式中的定界符
- (1) empty - 空模式 - 会匹配文本的每一行
- (2) /regular expression/ - 正则表达式匹配 - 仅处理能够被此处的模式匹配到的行
- (3) relational expression - 关系表达式 - 结果有真有假,结果为真才会被处理 - 假值为结果为0值或空字符串 - 真值为结果为非0值或非空字符串
- (4) line ranges - 行范围 - 地址定界 - 如startline,endline ==> /pat1/,/pat2/ - 注意,不支持直接给出数字的格式,需要使用NR
- (5) BEGIN/END模式 - awk的特点就是自动遍历文本的每一行,可以使用BEGIN/END进行限制 - BEGIN{} - 用于打印行首信息 - 仅在开始处理文件中的文本之前执行一次
- - `END{}` - 用于处理结果 - 仅在文本处理完成之后执行一次
# 正则表达式匹配
[root@localhost ~]# awk '/^UUID/{ print $1 }' /etc/fstab
UUID=6d0f6433-eed7-4cb2-81f5-2c4feaad2c18
# 正则表达式匹配,取反
[root@localhost ~]# awk '!/^UUID/{ print $1 }' /etc/fstab
/dev/mapper/VolGroup-lv_root
/dev/mapper/VolGroup-lv_swap
tmpfs
...
# 关系表达式
[root@localhost ~]# awk -F: '$3>=500{ print $1,$3 }' /etc/passwd
nfsnobody 65534
escape 500
[root@localhost ~]# awk -F: '$3<500{ print $1,$3 }' /etc/passwd
root 0
bin 1
daemon 2
...
# 模式匹配,$NF表示以:分割开的最后一个字段
[root@localhost ~]# awk -F: '$NF=="/bin/bash"{ print $1,$3 }' /etc/passwd
root 0
escape 500
mysql 27
[root@localhost ~]# awk -F: '$NF~/bash$/{ print $1,$3 }' /etc/passwd
root 0
escape 500
mysql 27
# 地址定界
[root@localhost ~]# awk -F: '(NR>=2 && NR<=10){print $1}' /etc/passwd
bin
daemon
...
# 指明模式
[root@localhost ~]# awk -F: '/^root/,/^adm/{ print $1 }' /etc/passwd
root
bin
daemon
adm
# BEGIN模式
# 这里添加的BEGIN表示执行一次,如果不添加则每次多执行
[root@localhost ~]# awk -F: 'BEGIN{ print "-------------------\n| useranme uid |\n-------------------"} { printf "%10s,%6s\n", $1,$3 }' /etc/passwd
-------------------
| useranme uid |
-------------------
root, 0
bin, 1
daemon, 2
...
# END模式
[root@localhost ~]# awk -F: 'BEGIN{ print "-------------------\n| useranme uid |\n-------------------"} { printf "%10s,%6s\n", $1,$3 } END{ print "=======END=======\n" }' /etc/passwd
-------------------
| useranme uid |
-------------------
root, 0
bin, 1
daemon, 2
...
========END========
3.6 常用的action
- (1) Expressions - 表达式
- (2) Control statements - 控制语句,如if、while等
- (3) Compound statements - 组合语句
- (4) input statements - 输入语句
- (5) output statements - 输出语句
3.7 控制语句
格式
- if(condition) {statments} - if语句
- if(condition) {statments} else {statements} - if…else语句
- while(conditon) {statments} - while语句
- do {statements} while(condition) - do语句
- for(expr1;expr2;expr3) {statements} - for语句
- break
- continue
- delete array - 删除一个数组
- delete array[index] - 从数字中删除一个指定元素
- exit - 退出语句
- { statements } - 组合语句需要花括号括起来使用
3.7.1if-else语句
语法
- if(condition) statement [else statement] - 组合语句需要花括号括起来使用
使用场景
- 对awk取得的整行或某个字段做条件判断
实例展示
# 双分支的if语句,需要{}
[root@localhost ~]# awk -F: '{if($3>=1000) { printf "Common user: %s\n",$1 } else { printf "root or Sysuser: %s\n",$1 } }' /etc/passwd
root or Sysuser: root
root or Sysuser: bin
root or Sysuser: daemon
...
# 单分支的if语句
[root@localhost ~]# awk -F: '{ if($NF=="/bin/bash") print $1 }' /etc/passwd
root
escape
mysql
[root@localhost ~]# awk '{ if(NF>5) print $0 }' /etc/fstab
/dev/mapper/VolGroup-lv_root / ext4 defaults 1 1
UUID=6d0f6433-eed7-4cb2-81f5-2c4feaad2c18 /boot ext4 defaults 1 2
...
# 这里的[],可以指定多个分隔符,如[%' ']表示%和空格作为分隔符
[root@localhost ~]# df -h | awk -F[%] '/^\/dev/{print $1}' | awk '{ if($NF>=10) print $1 }'
/dev/sda1
3.7.2while循环
语法
- while(condition) statement - 条件”真”,进入循环 - 条件”假”,退出循环
使用场景
- 对数组中的各元素逐一处理时使用
- 对一行内的多个字段逐一类似处理时使用
- 对于文本中的每行进行循环是不需要的,因为awk的特性
实例展示
# 在centos6中使用,其中内建函数length输出长度
[root@localhost ~]# awk '/^[[:space:]]*kernel/{ print }' /etc/grub.conf awk
kernel /vmlinuz-2.6.32-573.26.1.el6.x86_64 ro root=/dev/mapper/VolGroup-lv_root rd_NO_LUKS LANG=en_US.UTF-8 rd_NO_MD rd_LVM_LV=VolGroup/lv_swap SYSFONT=latarcyrheb-sun16 crashkernel=auto rd_LVM_LV=VolGroup/lv_root KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM rhgb quiet
[root@localhost ~]# awk '/^[[:space:]]*kernel/{ i=1; while(i<=NF) { print $i, length($i); i++ } }' /etc/grub.conf
kernel 6
/vmlinuz-2.6.32-573.26.1.el6.x86_64 35
ro 2
root=/dev/mapper/VolGroup-lv_root 33
...
[root@localhost ~]# awk '/^[[:space:]]*kernel/{ i=1; while(i<=NF) { if(length($i)>=7) { print $i, length($i) }; i++ } }' /etc/grub.conf
/vmlinuz-2.6.32-573.26.1.el6.x86_64 35
root=/dev/mapper/VolGroup-lv_root 33
rd_NO_LUKS 10
LANG=en_US.UTF-8 16
rd_NO_MD 8
# centos 7
[root@localhost ~]# awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {print $i,length($i); i++}}' /etc/grub2.cfg
3.7.3 do-while 循环
语法
- do statement while(condition)
意义
- 至少执行一次循环体
3.7.4 for 循环
语法
- for(expr1;expr2;expr3) statement
- for(variable assignment;condition;iteration process) {for-body}
特殊用法
- 能够遍历数组中的元素
- 语法:for(var in array) {for-body}
实例展示
# centos6
[root@localhost ~]# awk '/^[[:space:]]*kernel/{for(i=1;i<=NF;i++) {print $i,length($i)}}' /etc/grub.conf
kernel 6
/vmlinuz-2.6.32-573.26.1.el6.x86_64 35
...
[root@localhost ~]# awk '/^[[:space:]]*kernel/{for(i=1;i<=NF;i++) { if(length($i)>=7) print $i,length($i)}}' /etc/grub.conf
/vmlinuz-2.6.32-573.26.1.el6.x86_64 35
root=/dev/mapper/VolGroup-lv_root 33
# centos7
[root@localhost ~]# awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {print $i,length($i)}}' /etc/grub2.cfg
3.7.5switch语句
语法
- switch(expression) {case VALUE1 or /REGEXP/: statement; case VALUE2 or /REGEXP2/: statement; ...; default: statement} - 我们指定的表达式expression等于case中的值或模式匹配就执行对应的分支 - 组合语句需要花括号括起来 - default用于指定未匹配到的执行结果 - 以此类推
3.7.6 break 和 continue
- break [n] - 这里的[n]表示跳出多少条循环
- continue - 退出当前循环
3.7.7next语句
意义
- 用于结束awk的内生循环
- 提前结束对本行的处理而直接进入下一行
实战演示
# 显示用户id号为偶数的用户
[root@localhost ~]# awk -F: '{if($3%2!=0) next; print $1,$3}' /etc/passwd
root 0
daemon 2
lp 4
shutdown 6
...
3.8 数组array
关联数组
- array[index-expression]
index-expression
- (1) 可使用任意字符串,字符串要使用双引号 - 如weekdays[mon]="Monday"
- (2) 如果某数组元素事先不存在,在引用时awk会自动创建此元素,并将其值初始化为空串 - 使用不要声明 - 判断数组中元素是否存在,不能以空作为判断 - 判断数组中是否存在某元素,要使用index in array格式进行
遍历数组
- 若要遍历数组中的每个元素,要使用for循环
- for(var in array) {for-body}
注意
- var会遍历array的每个索引
- 使用数据来统计次数 - state["LISTEN"]++ - state["ESTABLISHED"]++
实战演示
[root@localhost ~]# awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday"; print weekdays["mon"]}'
Monday
[root@localhost ~]# awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";for(i in weekdays) {print weekdays[i]}}'
Monday
Tuesday
# 统计netstat的状态统计次数
# 其中/^tcp\>/中的>表示词尾牟定,只匹配tcp开头的
[root@localhost ~]# netstat -tan | awk '/^tcp\>/{ print }'
tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN
tcp 0 0 172.16.242.179:22 172.16.242.1:60761 ESTABLISHED
...
# state[$NF]表示将匹配到的行尾作为索引创建数组
# state[$NF]++表示统计次数
# END表示遍历完之后执行的输出操作
[root@localhost ~]# netstat -tan | awk '/^tcp\>/{state[$NF]++}END{for(i in state) {print i,state[i]}}'
ESTABLISHED 3
LISTEN 15
# 统计Web服务器的客户端IP访问次数统计
[root@localhost ~]# awk '{ip[$1]++}END{for(i in ip) {print i,ip[i]}}' /var/log/httpd/access_log
# 练习1:统计/etc/fstab文件中每个文件系统类型出现的次数
[root@localhost ~]# awk '/^UUID/{fs[$3]++}END{for(i in fs) {print i,fs[i]}}' /etc/fstab
# 练习2:统计指定文件中每个单词出现的次数
# 这里表示对每一行执行{for(i=1;i<=NF;i++){count[$i]++}}
[root@localhost ~]# awk '{for(i=1;i<=NF;i++){count[$i]++}}END{for(i in count) {print i,count[i]}}' /etc/fstab
3.9 函数
3.9.1 内置函数
数值处理
- 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中的小写o变为O
# 这里表示的是是否替换的返回值
[root@localhost ~]# awk -F: '{ print sub(o,O,$1) }' /etc/passwd
1
1
...
[root@localhost ~]# ab -c 100 -n 1000 http://172.16.242.180/index.html
# 对于得到的数组ip进行次数统计,count[ip[1]]++,最后输出即可
[root@localhost ~]# netstat -tan | awk '/^tcp\>/{split($5,ip,":");count[ip[1]]++}END{for (i in count) {print i,count[i]}}'
5
63.130.76.57 1
172.16.242.1 2
0.0.0.0 6
3.9.2 自定义函数
快去看看《sed和awk》这本书
4. 常用命令总结
OpenSource: 总结的快捷键使用文档!
文章作者: Escape
文章链接:
https://www.escapelife.site/posts/8548d5e9.html
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Escape !