Shell awk详解

一、awk介绍

awk其名称得自于它的创始人 Alfred Aho 、Peter Weinberger 和 Brian Kernighan 姓氏的首个字母。实际上 AWK 的确拥有自己的语言: AWK 程序设计语言 , 三位创建者已将它正式定义为“样式扫描和处理语言”

它允许您创建简短的程序,这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表,还有无数其他的功能。

awk 是一种很棒的语言,它适合文本处理和报表生成,其语法较为常见,借鉴了某些语言的一些精华,如 C 语言等。

awk程序由一个主输入循环(Main input loop)维持,主输入循环反复执行,直到条件被触发,主输入循环无须由程序员去写,awk已经搭好主输入循环的框架

二、awk模式匹配

任何awk语句都由模式(pattern)和动作(action)组成

  • 模式是由一组用于测试输入行是否需要执行动作的规则
  • 动作是包含语句,函数和表达式的执行过程

简言之,模式决定动作何时触发和触发事件,动作执行对输入行的处理

1. awk的第一种调用方式

[root@server1 ~]# touch input
[root@server1 ~]# awk '/^$/{print "This is a blank line."}' input
[root@server1 ~]# vim input 
[root@server1 ~]# awk '/^$/{print "This is a blank line."}' input
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
[root@server1 ~]# cat input 










其中:
单引号中间是awk命令,该awk命令由两部分组成,以/符号分隔,^$部分是模式,花括号部分是动作,该awk命令表示一旦读入的输入文件行是空行,就打印“This is a blank line”,^$是正则表达式,表示空白行,print表示该动作是打印操作,input是输入文件名称

2:awk的第二种调用方式

使用awk -f 调用scr.awk文件

[root@server1 ~]# vim scr.awk
[root@server1 ~]# awk -f scr.awk input 
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
[root@server1 ~]# cat scr.awk 
/^$/{print "This is a blank line."}

3. 直接使用awk脚本文件的方法来调用

这种方式需要在脚本的开头加#!/bin/awk -f

[root@server1 ~]# vim scr.awk 
[root@server1 ~]# chmod o+x scr.awk 
[root@server1 ~]# ./scr.awk input 
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
This is a blank line.
[root@server1 ~]# cat scr.awk 
#!/bin/awk -f				
#第一行表示用什么来执行脚本
/^$/{print "This is a blank line."}

三、记录和域

awk认为输入文件是结构化的,awk将每个输入文件行定义为记录,行中的每个字符串定义为,域之间用空格,Tab键或其他符号进行分隔,分隔域的符号就叫做分隔符
awk定义域操作符$来指定执行动作的域,域操作符$后面跟数字或变量来标识域的位置,每条记录的域从1开始编号,如$1表示第一个域 $0表示所有域

1. 打印指定的域

[root@server1 ~]# awk '{print $2,$1,$4,$3}' stucoed 
Li Tom xian 028-12345678
Li Tom xian 028-12345678
Li Tom xian 028-12345678
Li Tom xian 028-12345678
Li Tom xian 028-12345678
Li Tom xian 028-12345678
Li Tom xian 028-12345678
Li Tom xian 028-12345678
[root@server1 ~]# cat stucoed 
Tom Li 028-12345678  xian
Tom Li 028-12345678  xian
Tom Li 028-12345678  xian
Tom Li 028-12345678  xian
Tom Li 028-12345678  xian
Tom Li 028-12345678  xian
Tom Li 028-12345678  xian
Tom Li 028-12345678  xian

2. 打印全部的域

[root@server1 ~]# awk '{print $0}' stucoed Tom Li 028-12345678  xian
Tom Li 028-12345678  xian
Tom Li 028-12345678  xian
Tom Li 028-12345678  xian
Tom Li 028-12345678  xian
Tom Li 028-12345678  xian
Tom Li 028-12345678  xian
Tom Li 028-12345678  xian

3.域操作符$后面还可以跟变量,或者变量运算表达式

[root@server1 ~]# awk 'BEGIN {one=1;two=2} {print $(one+two)}' stucoed 
028-12345678
028-12345678
028-12345678
028-12345678
028-12345678
028-12345678
028-12345678
028-12345678

4.awk默认的设置是空格键,Tab键被看作连续的空格键来处理

[root@server1 ~]# cat stucoed 
Tom Li		 028-12345678  xian
Tom Li		 028-12345678  xian
Tom Li 028-12345678  xian
Tom Li 028-12345678  xian
Tom Li 028-12345678  xian
Tom Li 028-12345678  xian
Tom Li 028-12345678  xian
Tom Li 028-12345678  xian
[root@server1 ~]# awk 'BEGIN {one=1;two=2} {print $(one+two)}' stucoed 
028-12345678
028-12345678
028-12345678
028-12345678
028-12345678
028-12345678
028-12345678
028-12345678

5.可以使用-F来改变分隔符

注意看下面的例子 电话和xian之间用空格分开的化被看作是一个域
其中:\t表示tab,Tom和Li用tab分割,Li与电话之间用tab分隔

[root@server1 ~]# cat stucoed
Tom	Li		028-12345678 xian
Tom Li		028-12345678 xian
Tom Li		028-12345678 xian
Tom Li 		028-12345678 xian
Tom Li 		028-12345678 xian
Tom Li		028-12345678 xian
Tom Li		028-12345678 xian
Tom Li		028-12345678 xian
[root@server1 ~]# awk -F "\t" '{print $3}' stucoed 
028-12345678 xian
028-12345678 xian
028-12345678 xian
028-12345678 xian
028-12345678 xian
028-12345678 xian
028-12345678 xian
028-12345678 xian

6.尽管-F选项可以改变分隔符 但是 awk还提供了另一种更方便,实用的方法来改变分隔符,这就是使用awk环境变量FS

我们可以通过在BEGIN字段中设置FS的值来改变分隔符

[root@server1 ~]# awk 'BEGIN {FS=","} {print  $0}' stucoed 
Tom,Li,028-12345678 xian
Tom,Li,028-12345678 xian
Tom,Li,028-12345678 xian
Tom,Li,028-12345678 xian
Tom,Li,028-12345678 xian
Tom,Li,028-12345678 xian
Tom,Li,028-12345678 xian
Tom,Li,028-12345678 xian

7. 打印第1域和第3域

[root@server1 ~]# awk 'BEGIN {FS=","} {print  $1,$3}' stucoed 
Tom 028-12345678 xian
Tom 028-12345678 xian
Tom 028-12345678 xian
Tom 028-12345678 xian
Tom 028-12345678 xian
Tom 028-12345678 xian
Tom 028-12345678 xian
Tom 028-12345678 xian

对于不同的输入文件 需要根据实际情况设置相应的分隔符 如linux的/etc/passwd文件是以冒号为分隔符的

四、关系和布尔运算符

awk定义了一组关系运算符用于awk模式匹配

1.使用匹配正则表达式符号

第一个域匹配root

[root@server1 ~]# awk 'BEGIN {FS=":"} $1~/root/' /tmp/passwd 
root:x:0:0:root:/root:/bin/bash

全部域匹配root

[root@server1 ~]# awk 'BEGIN {FS=":"} $0~/root/' /tmp/passwd 
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

全部域不匹配nologin

[root@server1 ~]# awk 'BEGIN {FS=":"} $0!~/nologin/' /tmp/passwd 
root:x:0:0:root:/root:/bin/bash
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
dd:x:1000:1000::/home/dd:/bin/bash

2.awk在进行模式匹配时,常常使用到条件语句

awk条件语句与C语言类似

注意:if后面的条件需要用圆括号括起来

[root@server1 ~]# awk 'BEGIN {FS=":"} {if ($3<$4) print $0}' /tmp/passwd 
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin

多条件精确匹配

在文件中查找满足$3==1或者$4==10的记录

利用==符号进行的匹配可称为精确匹配

[root@server1 ~]# awk 'BEGIN {FS=":"} {if ($3==1||$4==10) print $0}' /tmp/passwd 
bin:x:1:1:bin:/bin:/sbin/nologin

多条件模糊匹配
==改为~符号 表示模糊匹配 所查询内容包含1或者10这个字符就可以

[root@server1 ~]# awk 'BEGIN {FS=":"} {if ($3~1||$4~10) print $0}' /tmp/passwd 
bin:x:1:1:bin:/bin:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
dd:x:1000:1000::/home/dd:/bin/bash

五、表达式

与其他编程语言一样,awk表达式用于存储,操作和获取数据
一个awk表达式可由数值,字符常量,变量,操作符 函数和正则表达式自由组合而成变量是一个值的标识符,定义awk变量非常方便,只需定义一个变量名并将值赋给它即可
其中:

  • 变量名只能包含字母,数字和下划线 而且不能以数字开头
  • 定义awk变量无须声明变量类型,每个变量有两种类型的值:字符串值和数值
  • awk根据表达式上下文来确定使用哪个值
  • 变量的默认数值为0,默认字符串值为空

1.统计input文件中的空白行

[root@server1 ~]# awk '/^$/{print x+=1}' input 
1
2
3
4
5
6
7
8
9

++x /x++ --x/x-- 的区别
x++:即返回x值后,x变量增加1
++x:即x变量增加1,再返回x值

[root@server1 ~]# awk '/^$/{print x++}' input 
0
1
2
3
4
5
6
7
8
[root@server1 ~]# awk '/^$/{print ++x}' input 
1
2
3
4
5
6
7
8
9

2.计算平均值的例子

[root@server1 ~]# vim scr2.awk 
[root@server1 ~]# cat stucoed 
Tom,Li,028-1234567 85,92,97,88
Tom,Li,028-1234567 85,92,97,88
Tom,Li,028-1234567 85,92,97,88
Tom,Li,028-1234567 85,92,97,88
Tom,Li,028-1234567 85,92,97,88
Tom,Li,028-1234567 85,92,97,88
Tom,Li,028-1234567 85,92,97,88
Tom,Li,028-1234567 85,92,97,88
Tom,Li,028-1234567 85,92,97,88
Tom,Li,028-1234567 85,92,97,88
Tom,Li,028-1234567 85,92,97,88
Tom,Li,028-1234567 85,92,97,88
Tom,Li,028-1234567 85,92,97,88
Tom,Li,028-1234567 85,92,97,88
[root@server1 ~]# vim scr2.awk 
[root@server1 ~]# vim stucoed 
[root@server1 ~]# vim scr2.awk 
[root@server1 ~]# chmod +x scr2.awk  #给脚本执行权限
[root@server1 ~]# ./scr2.awk stucoed  #输出学生姓名和平均成绩
Tom 72.4
Tom 72.4
Tom 72.4
Tom 72.4
Tom 72.4
Tom 72.4
Tom 72.4
Tom 72.4
Tom 72.4
Tom 72.4
Tom 72.4
Tom 72.4
Tom 72.4
[root@server1 ~]# cat scr2.awk 
#!/bin/awk -f
BEGIN {FS=","}
{total=$4+$5+$6+$7+$8
avg=total/5
print $1,avg}

六、系统变量

awk定义了很多内建变量用于设置环境信息,我们称它为系统变量

这些系统变量可分为:

  • 第一种用于改变awk的默认值,如域分隔符
  • 第二种用于定义系统值,在处理文本时可以读取这些系统值 如记录中的域数量,当前记录数,当前文件名等
符号 含义
$n 当前记录的第n个域 域间由FS分隔
$0 记录所有的域
FS 字段分隔符,默认是空格键
NF 当前记录中的域数量
NR 当前记录数(被awk处理过的行数)
FILENAME 保存了当前的输入文件名
[root@server1 ~]# awk 'BEGIN {FS=","} {print NF,NR,$0} END {print FILENAME}' stucoed 
7 1 Tom,Li,028-1234567,85,92,97,88
7 2 Tom,Li,028-1234567,85,92,97,88
7 3 Tom,Li,028-1234567,85,92,97,88
7 4 Tom,Li,028-1234567,85,92,97,88
7 5 Tom,Li,028-1234567,85,92,97,88
7 6 Tom,Li,028-1234567,85,92,97,88
7 7 Tom,Li,028-1234567,85,92,97,88
7 8 Tom,Li,028-1234567,85,92,97,88
7 9 Tom,Li,028-1234567,85,92,97,88
7 10 Tom,Li,028-1234567,85,92,97,88
7 11 Tom,Li,028-1234567,85,92,97,88
7 12 Tom,Li,028-1234567,85,92,97,88
7 13 Tom,Li,028-1234567,85,92,97,88
7 14 Tom,Li,028-1234567,85,92,97,88
stucoed

七、格式化输出

前面的例子只涉及awk如何输入文件进行处理,对于输出的格式并未规定。而awk的一大主要功能是产生报表,报表就是要求按照于定的格式输出,awk借鉴c语言的语法,定义了printf输出语句,它可以规定输出的格式

示例1:

实现要求:
从域号获取值 $2号域与%s对应 为字符串,$8号域与%d对应 为整数值,两个域之间用Tab键隔开(\t:Tab键),每输出两个域换行(\n表示换行)

[root@server1 ~]# awk 'BEGIN {FS=","} {printf("%s\t%d\n",$2,$8)}' stucoed 
Li	0
Li	0
Li	0
Li	0
Li	0
Li	0
Li	0
Li	0
Li	0
Li	0
Li	0
Li	0
Li	0
Li	0

2.ASCII字符的转换

[root@server1 ~]# awk 'BEGIN {printf("%c\n",65)}' 
A

3.浮数的转换

[root@server1 ~]# awk 'BEGIN {printf("%f\n",2010)}' 
2010.000000

4.printf修饰符的例子
以字符串格式输出文件的第1和第3号域,并对第1个%s进行了修饰
-15表示该字符串长度控制为15位,并且左对齐,若字符串不足15位 则用空格补齐

[root@server1 ~]# awk 'BEGIN {FS=","} {printf("%-15s\t%s\n",$1,$3)}' stucoed 
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567

[root@server1 ~]# awk 'BEGIN {FS=","} {printf("%-15s\t%s\n",$1,$3)}' stucoed 
Tommmm         	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567

5.如果我们要在输出的域名上加解释语言 可以在BEGIN字段中添加相应的输出注释

[root@server1 ~]# awk 'BEGIN {FS=",";print "Name\t\tPhonenumber"} {printf("%-15s\t%s\n",$1,$3)}' stucoed 
Name		Phonenumber
Tommmm         	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567
Tom            	028-1234567

6.小数点后保留3位

[root@server1 ~]# awk 'BEGIN {printf("%.3f\n",2020.1212)}'
2020.121

八、内置字符串函数

awk提供了强大的内置字符串函数 用于实现文本的字符串替换 查找以及分割等功能

8.1 gsub函数

gsub函数执行字符串替换功能,它将第一个字符串替换为第二个字符串,gsub函数有两种形式,第一种形式作用于全部域 即$0 第二种形式作用于域t
OFS:输出域分隔符 默认是空格
1.替换第一域上的root字符串

[root@server1 ~]# awk 'BEGIN {FS=":";OFS=":"} gsub(/root/,"dd", $1) {print $0}' /tmp/passwd 
dd:x:0:0:root:/root:/bin/bash

2.替换全部域上的root字符串

[root@server1 ~]# awk 'BEGIN {FS=":";OFS=":"} gsub(/root/,"dd") {print $0}' /tmp/passwd 
dd:x:0:0:dd:/dd:/bin/bash
operator:x:11:0:operator:/dd:/sbin/nologin

8.2 index和length函数的用法

1. index返回第二个字符串在第一个字符串出现的首位置

[root@server1 ~]# awk 'BEGIN {print index("gridsphere","ph")}'
6

2. length返回字符串的长度

[root@server1 ~]# awk 'BEGIN {print length("gridsphere")}'
10

8.3match(s,t)测试s是否包含t的字符串

t可以是一个正则表达式 若匹配成功 返回匹配t的首位置
若匹配不成功 则返回0
1.在gridsphere字符串中匹配D/d

awk默认状态是区分大小写的

[root@server1 ~]# awk 'BEGIN {print match("gridsphere",/D/)}'
0 #匹配不成功返回0
[root@server1 ~]# awk 'BEGIN {print match("gridsphere",/d/)}'
4

2.先定义str变量,赋值,然后用sub函数讲pro改成大写的PRO str有两个pro
结果显示只有一个pro改成了大写的PRO

[root@server1 ~]# awk 'BEGIN {str="multiprocessor programming";sub(/pro/,"PRO",str);printf("%s\t",str)}'
multiPROcessor programming
发布了101 篇原创文章 · 获赞 65 · 访问量 3145

猜你喜欢

转载自blog.csdn.net/qq_35887546/article/details/104327540