Linux 文本处理三剑客 ,grep ,sed,awk。Grep是文本过滤工具,sed文本行编辑器,前两者我们已经在前面的博客中做出了介绍,今天介绍awk,awk是一种报表生成器,也就是对文件进行格式化处理,这里的格式化不是文件系统的格式化,而是对文件内容进行各种“排版”,进行格式化显示
在linux上我们使用的是GUN awk简称gawk,gawk是一种过程式编程语言。既然是一种语言那么他就支持条件判断,数组,循环等各种编程语言中所有可以使用的功能,我们可以把gawk称为一种脚本语音解释器。
简单的awk
[root@localhost ~]#awk '{ print }' /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologindaemon:x:2:2:daemon:/sbin:/sbin/nologinadm:x:3:4:adm:/var/adm:/sbin/nologinlp:x:4:7:lp:/var/spool/lpd:/sbin/nologin..............
liang:x:1004:1004::/home/liang:/bin/bash
zhaoyun:x:1005:1005::/home/zhaoyun:/bin/bash[root@localhost ~]# awk '{ print }' /etc/passwd|wc -l
48[root@localhost ~]# cat /etc/passwd |wc -l48/etc/passwd 文件的内容出现在眼前。现在,解释 awk 做了些什么。调用 awk 时,我们指定 /etc/passwd 作为输入文件。执行 awk 时,它依次对 /etc/passwd 中的每一行执行 print 命令。所有输出都发送到 stdout,所得到的结果与与执行cat /etc/passwd完全相同。
现在,解释 { print }
代码块。在 awk 中,花括号用于将几块代码组合到一起,这一点类似于 C 语言。在代码块中只有一条 print 命令。在 awk 中,如果只出现 print 命令,那么将打印当前行的全部内容。
另一个 awk 示例,它的作用与上例完全相同
[root@localhost ~]# awk '{ print $0 }' /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologindaemon:x:2:2:daemon:/sbin:/sbin/nologinadm:x:3:4:adm:/var/adm:/sbin/nologinlp:x:4:7:lp:/var/spool/lpd:/sbin/nologin..............
liang:x:1004:1004::/home/liang:/bin/bash
zhaoyun:x:1005:1005::/home/zhaoyun:/bin/bash在 awk 中,$0
变量表示整个当前行,所以 print
和 print $0
的作用完全一样。同时我们从前两个例子中也可以看出awk和sed一样是一行一行处理数据的,对于awk来说每一行内容是一个数据。
如果您愿意,可以创建一个 awk 程序,让它输出与输入数据完全无关的数据。以下是一个示例
[root@localhost ~]# awk '{ print "" }' /etc/passwd
...
[root@localhost ~]# awk '{ print "" }' /etc/passwd|wc -l
48只要将 "" 字符串传递给 print 命令,它就会打印空白行。如果测试该脚本,将会发现对于 /etc/passwd 文件中的每一行,awk 都输出一个空白行。再次说明, awk 对输入文件中的每一行都执行这个脚本。以下是另一个示例
[root@localhost ~]# awk '{ print "hi" }' /etc/passwd
hihihihi....
hi
[root@localhost ~]# awk '{ print "hi" }' /etc/passwd|wc -l
48awk 工作原理
-
awk 使用一行作为输入(通过文件或者管道),并将这一行赋给内部变量 $0
-
行被空格分解为字段(单词),每一个字段存储在已编号的变量中,从 $1 开始。( awk 的内部变量 FS 用来确定字段的分隔符。初始时,为空格,包含制表符和空格符)
-
对于一行,按照给定的正则表达式的顺序进行匹配,如果匹配则执行对应的 Action ,如果没有匹配上则不执行任何动作 , Search Pattern 和 Action 是可选的,但是必须提供其中一个 。如果 Search Pattern 未提供,则对所有的输入行执行 Action 操作。如果 Action 未提供,则默认打印出该行的数据 。 {} 这种 Action 不做任何事情,和未提供的 Action 的工作方式不一样
-
打印字段,用 print 、 printf 、 sprintf ,格式: { print $1, $3 } 内部变量 output field separator ( OFS ),默认为空格, $n 之间的逗号被 OFS 中的字符替换。
-
输出之后,从文件中另取一行,并将其复制到 $0 中,覆盖原来的内容。重复进行……
Print与printf
print 函数用于打印不需要特别编排格式的简单输出。更为复杂的格式编排则要使用 printf 和 sprintf 。若懂得 C 语言,则也一定懂得如何使用 printf 和 sprintf 。
print 函数的的转义序列
/b 退格
/f 换页
/n 换行
/r 回车
/t 制表符
/047 八进制值 47 ,即单引号
/c c 代表任意其他字符
打印数字时,可能需要控制数字的格式。可以通过 printf 来实现,但是通过设置一个特殊的变量 OFMT ,是用 print 函数也可以控制数字打印格式。 OFMT 默认为“ %.6gd” ,表示只打印小数部分的前 6 位。
[root@localhost ~]# awk 'BEGIN { OFMT="%.2f"; print 1.2456789, 1.234-2 }'
1.25 -0.77[root@localhost ~]# awk 'BEGIN { printf "%.2f %.2f\n" ,1.2456789, 1.234-2 }'
1.25 -0.77
printf 函数
printf 函数返回一个带格式的字符串给标准输出,如同 C 语言中的 printf 语句。 printf 语句包括一个加引号的控制串,控制串中可能嵌套有若干格式说明和修饰符。控制串后面跟逗号,之后是一列由逗号分隔的表达式。与 print 函数不同的是, printf 函数不会在行尾自动换行。若要换行,在控制串中提供转义字符 /n 。每个百分号和格式说明都必须有一个对应的变量 。要打印百分号就必须在控制串中给出两个百分号。
printf 函数的转义字符
c 字符
s 字符串
d 十进制整数
ld 十进制长整数
u 十进制无符号整数
lu 十进制无符号长整数
x 十六进制整数
lx 十六进制长整数
o 八进制整数
lo 八进制长整数
e 用科学计数法表示浮点数
f 浮点数
g 选用 e 或 f 中较短的一种形式
printf 函数的修饰符
- 左对齐修饰符
# 显示八进制整数时,前面加 0 ,显示十六进制整数时,前面加 0x
+ 显示使用 d 、 e 、 f 、 g 转换的整数时,加上正负号
0 用 0 而不是空白符来填充所显示的值
printf 函数控制串里的管道符(竖杠)是文本的一部分,用于指示格式的起始与结束。
|[root@localhost ~]#echo "UNIX" | awk ' { printf "|%-15s|\n", $11 }' #%15s表示15个字符的字符串若没有则用空格代替
|UNIX |[root@localhost ~]# echo "UNIX" | awk ' { printf "|%-15s\n|", $1 }'|UNIX |[root@localhost ~]#BEGIN与END
通常,对于每个输入行, awk 都会执行每个脚本代码块一次。然而,在许多编程情况中,可能需要在 awk 开始处理输入文件中的文本之前执行初始化代码。对于这种情况, awk 允许您定义一个 BEGIN 块。
因为 awk 在开始处理输入文件之前会执行 BEGIN 块,因此它是初始化 FS(字段分隔符)变量、打印页眉或初始化其它在程序中以后会引用的全局变量的极佳位置。
awk 还提供了另一个特殊块,叫作 END 块。 awk 在处理了输入文件中的所有行之后执行这个块。通常, END 块用于执行最终计算或打印应该出现在输出流结尾的摘要信息。
有一点要明确就是BEGIN{}与END{}仅且只执行一次
[root@localhost ~]# awk 'BEGIN {print 1}{print 2}END{print 3}' /etc/passwd1222...23
Awk运算符
算术操作符:
x+y, x-y, x*y, x/y, x^y, x%y
-x: 转换为负数
+x: 转换为数值
字符串操作符:没有符号的操作符,字符串连接
赋值操作符:
=, +=, -=, *=, /=, %=, ^=
++, --
比较操作符:
==, !=, >, >=, <, <=
模式匹配符:~:左边是否和右边匹配包含!~:是否不匹配
[root@localhost ~]#awk –F: '$0 ~ /root/{print $1}' /etc/passwd root operator [root@localhost ~]#awk –F: '$3==0' /etc/passwd root:x:0:0:root:/root:/bin/bash
逻辑操作符:与&&,或||,非!
示例:
[root@localhost ~]# awk -F: '$3>=0 && $3<=1000 {print $1}' /etc/passwd
rootbindaemonadmlp......
liang
zhaoyun[root@localhost ~]# awk -F: '$3==0 || $3>=1000 {print $1}' /etc/passwd
rootnfsnobodycentoszhangsanlisililiangzhaoyun[root@localhost ~]# awk -F: '!($3==0) {print $1}' /etc/passwd
bindaemonadmlpsyncshutdown.....
liang
zhaoyun[root@localhost ~]# awk -F: '!($3>=500){print $3}' /etc/passwd
012345678...74708972条件表达式(三目表达式):
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 adm:Sysadmin or SysUser lp:Sysadmin or SysUser sync:Sysadmin or SysUser shutdown:Sysadmin or SysUser halt:Sysadmin or SysUser mail:Sysadmin or SysUser operator:Sysadmin or SysUser games:Sysadmin or SysUser ftp:Sysadmin or SysUser nobody:Sysadmin or SysUsersystemd-network:Sysadmin or SysUser dbus:Sysadmin or SysUser polkitd:Sysadmin or SysUser sssd:Sysadmin or SysUser libstoragemgmt:Sysadmin or SysUser rpc:Sysadmin or SysUser colord:Sysadmin or SysUser gluster:Sysadmin or SysUser saslauth:Sysadmin or SysUser abrt:Sysadmin or SysUser setroubleshoot:Sysadmin or SysUser rtkit:Sysadmin or SysUser pulse:Sysadmin or SysUser chrony:Sysadmin or SysUser rpcuser:Sysadmin or SysUser nfsnobody:Common User unbound:Sysadmin or SysUser tss:Sysadmin or SysUser usbmuxd:Sysadmin or SysUser geoclue:Sysadmin or SysUser radvd:Sysadmin or SysUser qemu:Sysadmin or SysUser ntp:Sysadmin or SysUser gdm:Sysadmin or SysUsergnome-initial-setup:Sysadmin or SysUser sshd:Sysadmin or SysUser avahi:Sysadmin or SysUser postfix:Sysadmin or SysUser tcpdump:Sysadmin or SysUser centos:Common User zhangsan:Common User lisi:Common User li:Common User liang:Common User zhaoyun:Common UserAwk变量及
自定义变量(区分字符大小写)
(1) -v var=value (2) 在program中直接定义示例:[root@localhost ~]# awk -v test='hello gawk' '{print test}' /etc/fstab
hello gawkhello gawkhello gawkhello gawkhello gawkhello gawkhello gawkhello gawkhello gawkhello gawkhello gawkhello gawk[root@localhost ~]# awk -v test='hello gawk' 'BEGIN{print test}'
hello gawk
awk中常见内置变量
变量名 | 属性 |
FS | 输入字段分隔符,默认为空白字符 |
OFS | 输出字段分隔符,默认为空白字符 |
RS | 输入记录分隔符,指定输入时的换行符 |
ORS | 输出记录分隔符,输出时用指定符号代替换行 |
NF | 字段数量 |
NR | 行号 |
FNR | 各文件分别计数,行号 |
FILENAME | 当前文件名 |
ARGC | 命令行参数的个数 |
ARGV | 数组,保存的是命令行所给定的各参数 |
Awk 的If,循环,与数组
If
语法:
if(condition){statement;…}[else statement]
if(condition1){statement1}else if(condition2){statement2} else{statement3}使用场景:对awk取得的整行或某个字段做条件判断
示例:[root@localhost ~]# awk -F: '{if($3>=1000)print $1,$3}' /etc/passwd
nfsnobody 65534centos 1000zhangsan 1001lisi 1002li 1003liang 1004zhaoyun 1005[root@localhost ~]# awk -F: '{if($NF=="/bin/bash") print $1}' /etc/passwd
rootcentoszhangsanlisililiangzhaoyunWhile
语法:
while(condition){statement;…}
条件“真”,进入循环;条件“假”,退出循环使用场景: 对一行内的多个字段逐一类似处理时使用 对数组中的各元素逐一处理时使用示例:[root@localhost ~]# awk '/^[[:space:]]*linux16/{i=1;while(i<=NF){print $i,length($i); i++}}' /etc/grub2.cfg
linux16 7/vmlinuz-3.10.0-862.el7.x86_64 30root=UUID=0c69d6ed-c758-4a3d-a73b-dbcfaca2d817 46ro 2crashkernel=auto 16rhgb 4quiet 5LANG=zh_CN.UTF-8 16linux16 7/vmlinuz-0-rescue-e650feaecc624ac7ac2823eaad4cf6ca 50root=UUID=0c69d6ed-c758-4a3d-a73b-dbcfaca2d817 46ro 2crashkernel=auto 16rhgb 4quiet 5For
语法:
for(expr1;expr2;expr3) {statement;…}
常见用法: for(variable assignment;condition;iterationprocess) {for-body}特殊用法:能够遍历数组中的元素
语法:for(varin array) {for-body}
示例:[root@localhost ~]# awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {print $i,length($i)}}' /etc/grub2.cfg
linux16 7/vmlinuz-3.10.0-862.el7.x86_64 30root=UUID=0c69d6ed-c758-4a3d-a73b-dbcfaca2d817 46ro 2crashkernel=auto 16rhgb 4quiet 5LANG=zh_CN.UTF-8 16linux16 7/vmlinuz-0-rescue-e650feaecc624ac7ac2823eaad4cf6ca 50root=UUID=0c69d6ed-c758-4a3d-a73b-dbcfaca2d817 46ro 2crashkernel=auto 16rhgb 4quiet 5同样在awk中同样支持break与countinue
[root@localhost ~]# awk -F: '{if($3%2!=0) next; print $1,$3}' /etc/passwdroot 0daemon 2lp 4shutdown 6mail 8games 12ftp 14systemd-network 192sssd 998rpc 32colord 996saslauth 994rtkit 172chrony 992nfsnobody 65534geoclue 990ntp 38gdm 42sshd 74avahi 70tcpdump 72centos 1000lisi 1002liang 1004
数组
数组在 awk 中被称为关联数组 ( associative arrays ),其下表既可以是字符串也可以是数字。数组的键和值都存储在 awk 程序内部的一个表中,该表采用的是 散列算法,所以数组元素不是顺序存储的。数组也是被用到时才被创建。 awk 还能判定数组用于保存数字还是字符串,根据上下文被初始化为 0 或者空字符串。数组大小不需要声明。
当下标为字符串或者非连续的数字时,不能用 for 循环来遍历数组。这是就要使用特殊的 for 循环。
[root@localhost ~]# cat test
4234 Tom 434571 Tom 223298 Eliza 214622 Tom 532345 Mary 24[root@localhost ~]# awk '{count[$2]++};END{for(name in count)printf"%s %s\n" ,name,count[name]}' test
Tom 3Eliza 1Mary 1Awk常用的内部函数
数值处理:
rand():返回0和1之间一个随机数
[root@localhost ~]# awk 'BEGIN{srand(); for (i=1;i<=10;i++)print int(rand()*100) }'
98828518321840986380字符串处理:
length([s]):返回指定字符串的长度
sub(r,s,[t]):对t字符串进行搜索r表示的模式匹配的内容,并将第一个匹配的内容替换为s[root@localhost ~]# echo "2008:08:08 08:08:08" |awk 'sub(/:/,"-",$1)'
2008-08:08 08:0808gsub(r,s,[t]):对t字符串进行搜索r表示的模式匹配的内容,并全部替换为s所表示的内容
[root@localhost ~]# echo "2008:08:08 08:08:08" | awk 'gsub(/:/,"-",$0) '
2008-08-08 08-08-08split(s,array,[r]):以r为分隔符,切割字符串s,并将切割后的结果保存至array所表示的数组中,第一个索引值为1,第二个索引值为2,…
[root@localhost ~]# netstat -tan | awk '/^tcp\>/{split($5,ip,":");count[ip[1]]++};END{for (i in count) {print i,count[i]}}'
192.168.75.1 10.0.0.0 6Awk中调用shell命令
system命令
空格是awk中的字符串连接符,如果system中需要使用awk中的变量可以使用空格分隔,或者说除了awk的变量外其他一律用""引用起来。[root@localhost ~]# awk BEGIN'{system("hostname") }'
localhost.localdomain[root@localhost ~]# awk 'BEGIN{score=100; system("echo your score is " score) }'
your score is 100