纸上得来终觉浅,绝知此事要躬行。
1. 什么是awk
awk
是一个报告生成器,可以格式化文本输出。
- 通常我们在
Linux
下使用的awk
,其实是gawk
awk
默认以空格作为分隔符号,整个字段使用$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.1 print
命令
格式
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.3 printf
命令
格式化输出
- 格式
printf FORMAT, item1, item2, ...
- 注意
- (1)
FORMAT
必须给出 - (2) 不会自动换行,需要显式给出换行控制符,
\n
- (3)
FORMAT
中需要分别为后面的每个item
指定一个格式化符号
- (1)
格式符
%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.5 PATTERN
匹配
类似于正则表达式中的定界符
(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.1 if-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.2 while
循环
语法
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.5 switch
语句
语法
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.7 next
语句
意义
- 用于结束
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: 总结的快捷键使用文档!