文章目录
文本处理之awk命令高级用法
1. 基本用法
例如,有一个文件的内容是这样的。
[root@kiwi222 ~]# cat 111
this is hello world
现在将里面的hello world提取出来
[root@kiwi222 ~]# awk '{ print "hello world"}' 111
hello world
[root@kiwi222 ~]# awk '{ print "hello,world"}' 111
hello,world
[root@kiwi222 ~]#
[root@kiwi222 ~]# awk '{ print }' 111
this is hello world
[root@kiwi222 ~]#
## print是将提取的给打印出来,print语句没有参数,只简单的输出每个输入行
从上面的例子可以看出awk是输入驱动的。也就是说,除非有可以在其上操作的输入行,否则将什么也不能做。
当调用 awk 程序时,它将读入所提供的脚本并检查其中的指令的语法。然后 awk 将对每个输入行执行脚本中的指令。因此,如果没有来自文件中的输入行,以上的 print 语句将不做任何事情。
2. awk程序设计模型
awk 程序是由所谓的主输入 (main input) 循环组成的。一个循环是一个例程,它将一直重复执行知道有一些存在的条件终止它。
其程序设计遵循以下模型:
- 读取输入:AWK程序从输入文件或标准输入读取文本行,逐行处理。
- 分割字段:默认情况下,AWK将输入行根据空格(或其他指定的字段分隔符)分割为多个字段。
- 匹配模式:使用模式(可以是正则表达式)来匹配符合特定条件的行。可以在模式操作之前或之后执行相关的操作。
- 执行操作:针对匹配的行,AWK执行指定的操作。这些操作可以是内置的,也可以是自定义的函数。
- 处理字段:在操作过程中,可以直接访问并处理单个字段,例如打印、计算、赋值等操作。
- 控制流程:AWK程序支持条件语句(如if-else、while等)和循环语句,以根据需要控制程序的流程。
- 输出结果:根据需要,AWK可以通过print语句或其他方式将结果输出到屏幕上或写入输出文件。
- 重复步骤:AWK程序会重复执行以上步骤,直到处理完所有的输入行。
awk 允许你编写两个特殊的例程,他们在任何输入被读取前和所有输入都被读取后执行。他们是与 BEGIN 和 END 规则相关的过程。换句话说,在主输入循环执行前和主输入循环钟之后你可以做一些处理。BEGIN和 END 过程是可选的。
也就是说
可以把awk脚本看做由3个主要部分组成:
处理输入前将要做的处理
处理输入过程中将做的处理
处理输入完成后做的处理
例如:
[root@kiwi222 ~]# awk 'BEGIN{print "hello world"}'
hello world
上面这个例子中可以把它看成三个部分
BEGIN为第一个部分,主体为第二个部分,END为第三个部分
-
如果我们写到BEGIN里面,它就不需要任何处理的对象。
-
如果我们写到主体里面去,它就必须要有操作对象。
-
END就是等我们主体处理完之后做一个总结
但用的时候可只要BEGIN或者只出现主体,不能只写END。
写法如:
awk 'BEGIN{}{主体}END{}'
3. 模式匹配
当 awk 读入一行是时,它试图匹配脚本中的每个模式匹配规则。只有与一个特定的模式相匹配的输入行才能成为操作对象。如果没有指定操作,于模式相匹配的输入行将被打印出来 (执行打印语句是一个默认操作)
例如:
[root@kiwi222 ~]# cat kiwi222
kiwi123456
abc123
kiwi111:kiwi222
111:222
[root@kiwi222 ~]#
现将上面的文件指定操作
[root@kiwi222 ~]# awk '/^$/{ print "Null line"}' kiwi222
Null line
Null line
Null line
## 这里的意思就是如果文件kiwi222有空行的话就打印Null line这句话
/[0-9+]/ { print "This is number"}
## 匹配如果是数字则打印This is number这句话
4.记录和字段
在awk中,假设它的输入是有结构的,而不只是一串无规则的字符。
那么它会有字段以及记录。记录(record)是指输入数据中的一行文本,而字段(field)则是该行文本中由分隔符(默认是空格)分隔的部分(用来分隔字段的字符被称为分隔符)。
连续的两个或多个空格和/或制表符被作为一个分隔符。
4.1 字段和引用的分离
awk可以使用 $ 符号访问和处理这些字段。
例如,**$**1表示第一个字段,$2表示第二个字段,依此类推。
“$0“则表示整个输入记录
例如:
[root@kiwi222 ~]# cat kiwi333
kiwi111 123456 //abc
[root@kiwi222 ~]# awk '{ print $2, $3, $1 }' kiwi333
123456 //abc kiwi111
[root@kiwi222 ~]#
[root@kiwi222 ~]# awk '{ print $0 }' kiwi333
kiwi111 123456 //abc
[root@kiwi222 ~]#
## 这里的值中输出的逗号默认为空格
还可以用任何计算值为整数的表达式来表示一个字段,而不只是用数字和变量。
[root@kiwi222 ~]# echo a b c d | awk 'BEGIN { one = 1; two = 2 } { print $(one + two) }'
c
[root@kiwi222 ~]#
[root@kiwi222 ~]# awk 'BEGIN{print 10/4}'
2.5
[root@kiwi222 ~]#
还可以使用-F选项改变字段的分隔符
## 在它们中间加上:
[root@kiwi222 ~]# cat kiwi333
kiwi111 123456 //abc
kiwi111:123456://abc
[root@kiwi222 ~]#
[root@kiwi222 ~]# awk -F ':' '{ print $1 }' kiwi333
kiwi111 123456 //abc
kiwi111
[root@kiwi222 ~]# awk -F ':' '{ print $2 }' kiwi333
123456
4.2 字段的划分FS
在awk中,可以使用3个完全不同的方法使awk分隔字段。
- 第一种方法是用空白字符来分割字段
要实现这种方法,可将FS 设置为一个空格。在这种情况下,记录的前导空白字符和结尾空白字符 (空格和/或制表符) 将被忽略。并且字段空格和/或者制表位来分隔。因为FS 的默认值为一个空格,所以这也是通常情况下 awk 将记录划分为字段的方法。
- 第二种方法是使用其他单个字符来分隔字段。
当FS 表示任何单个字符时,在这个字符出现的任何地方都将分隔出另外一个字段。如果出现两个连续的分隔符,在它们之间的字段值为空串
- 第三种方法是如果设置了不止一个字符作为字段分隔符,那它将被作为一个正则表达式来解释。
例如
FS= “\t”
这个表示将每个制表符作为一个字段分隔符,
而:
FS=“\t+”
这个表示将每个制表符作为一个字段分割符
那如果要使用第二种方法,可以使用一个正则表达式指定几个字符作为分隔符
FS = "[:']"
## 这就表示用:以及'来作为分隔符
例如:
## 取出ip a 里面的ipv4地址
[root@kiwi222 ~]# ip a | grep 'inet'
inet 127.0.0.1/8 scope host lo
inet6 ::1/128 scope host
inet 192.168.234.33/24 brd 192.168.234.255 scope global noprefixroute ens160
inet6 fe80::20c:29ff:fe81:9835/64 scope link noprefixroute
[root@kiwi222 ~]# ip a | grep 'inet' | grep -v '127' | awk -F'[ /]+' '{ print $3 }'
192.168.234.33
[root@kiwi222 ~]#
## 上面这个例子中的awk -F'[ /]+' '{ print $3 }'用空格和/来作为分隔符,并匹配一次或者多次,然后打印第三个字符
5.表达式
5.1 转义序列
序列 | 描述 |
---|---|
\a | 报警字符,通常是ASCII BEL 字符 |
\b | 退格键 |
\f | 走纸符 |
\n | 换行符 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ddd | 将字符表示为1到3位八进制 |
\xbex | 将字符表示为十六进制值 |
\c | 任何需要字面表示的字符c(例如,“for”) |
5.2 算数操作符
操作符 | 描述 |
---|---|
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 |
% | 取模 |
^ | 取幂 |
** | 去幂 |
变量是引用值的标识符。定义变量只需要为它定义一个名字并将数据赋给它即
可。变量名只能由字母、数字和下划线组成。而且不能以数字开头。变量名的
大小写很重要: Salary 和 salary 是两个不同的变量,变量不必进行说明,你
不必告诉 awk 什么类型的数据存储在一个变量中。每个变量有一个字符串型值和数字型值,awk 能够根据表达式的前后关系来选择合适的值 (不包含数字的字符串值为 0)。变量不必初始化。awk 自动将它们初始化为空字符串,如果作为数字,它的值为 0。
列如:
[root@kiwi222 ~]# echo | awk 'BEGIN{x=4;y=x/4}{print y}'
1
[root@kiwi222 ~]#
5.3 统计空行数
[root@kiwi222 ~]# cat kiwi222
kiwi123456
abc123
kiwi111:kiwi222
111:222
[root@kiwi222 ~]#
[root@kiwi222 ~]# awk '/^$/ { print x += 1}' kiwi222
1
2
3
[root@kiwi222 ~]#
## 虽然这里没有为变量 x 赋初值。但在遇到第一个空行之前它的值一直为 0。表达式x+=1 在每次遇到空行时进行求值并将x 的值增加 1.print 语句打印表达式返回的值。因为我们在遇到每个空行时都执行 print 语句,所以我们得到了空行数的一个连续值。
还可以写成
++x 在返回结果前递增x的值 (前缀)
x++ 在返回结果后递增 x的值 (后缀)
“++”是递增操作符 (“”是递减操作符) 。表达式每计算一次变量的值就增加 1。递增和递减操作符可以出现在操作数的任何一边,与前缀或后缀操作符
样。位置不同可以得到不同的计算结果。
[root@kiwi222 ~]# awk '/^$/ { print x++ }' kiwi222
0
1
2
[root@kiwi222 ~]#
## 当遇到第一个空行时,表达式返回的值为“0“,遇到第二个空行时返回值为1,依此类推。如果将递增操作符放置与 x 的前面,当表达式第一次计算后,返回的值为“1”。
[root@kiwi222 ~]# awk '/^$/ { print ++x }' kiwi222
1
2
3
[root@kiwi222 ~]#
## 当遇到第一个空行时返回的值为1。
统计空行数
[root@kiwi222 ~]# awk '/^$/ { ++x } END { print x }' kiwi222
3
[root@kiwi222 ~]#
## 也就是说等主题处理完之后再去结果(END)打印总数
5.4计算平均数
例如:下面有几组数字
[root@kiwi222 ~]# cat avg
kiwi 55 66 87
jenny 78 89 54
xiaoming 44 88 74
[root@kiwi222 ~]#
下面算出它们的平均数
[root@kiwi222 ~]# awk '{ total = $2+$3+$4;avg=total/3;print $1,avg }' avg
kiwi 69.3333
jenny 73.6667
xiaoming 68.6667
[root@kiwi222 ~]#
6.系统变量
awk 中有许多系统变量或内置变量。awk 有两种类型的系统变量。第一种类型定义的变量默认值可以改变,例如默认的字段和记录分隔符。第二种类型定义的变量的值可用于报告或数据处理中。例如当前记录中字段的数量,当前记录的数量等。这些可以由 awk 自动更新,例如,当前记录的编号和输入文件名。
系统变量有以下几种:
- ARGC:命令行参数的数量。
- ARGV:一个数组,存储着命令行参数的值。
- FILENAME:当前正在处理的文件的名称。
- FNR:在当前文件中当前记录的记录号。
- NF:当前记录(行)中的字段数。
- NR:到目前为止读取的记录(行)数。
- OFS:输出字段分隔符。
- FS:输入字段分隔符。
- RS:输入记录分隔符。
- ORS:输出记录分隔符。
6.1 FS与OFS
FS:输入字段分隔符
OFS:输出字段分隔符
和FS等效的输出是OFS,它的默认值为一个空格。
例如:
[root@kiwi222 ~]# awk 'BEGIN{FS=" ";OFS=":"}{print $1,$3}' avg
kiwi:66
jenny:89
xiaoming:88
[root@kiwi222 ~]#
[root@kiwi222 ~]# awk 'BEGIN{FS=" ";OFS=":"}{print $1 $3}' avg
kiwi66
jenny89
xiaoming88
[root@kiwi222 ~]#
## 如果$1与$2中间为空格,意思就是不需要分隔符。也就是说想要使用分隔符的话逗号必须要加。
还可以为
[root@kiwi222 ~]# awk 'BEGIN{FS=" ";OFS="-"}{print $1,$3}' avg
kiwi-66
jenny-89
xiaoming-88
[root@kiwi222 ~]#
6.2 NR
到目前为止读取的记录(行)数。
例如:
[root@kiwi222 ~]# ip a | grep 'inet'
inet 127.0.0.1/8 scope host lo
inet6 ::1/128 scope host
inet 192.168.234.33/24 brd 192.168.234.255 scope global noprefixroute ens160
inet6 fe80::20c:29ff:fe81:9835/64 scope link noprefixroute
[root@kiwi222 ~]# ip a | grep 'inet' | awk -F'[ /]+' 'NR==3{ print $3 }'
192.168.234.33
[root@kiwi222 ~]#
## 打印第三行的第三个字符
6.3 NF
NF:当前记录(行)中的字段数。
[root@kiwi222 ~]# cat kiwi333
kiwi111 123456 //abc
kiwi111:123456://abc
[root@kiwi222 ~]# awk '{print NF}' kiwi333
3
1
[root@kiwi222 ~]#
提取最后一列
[root@kiwi222 ~]# awk '{print $NF}' kiwi333
//abc
kiwi111:123456://abc
[root@kiwi222 ~]#
提取倒数第二列
[root@kiwi222 ~]# awk '{print $(NF-1)}' kiwi333
123456
kiwi111:123456://abc
[root@kiwi222 ~]#
6.4 RS
RS:输入记录分隔符
ORS:输出记录分隔符
RS 输出等价的是 ORS,它的默认值也是一个换行符
7.处理多行记录
例如:
[root@kiwi222 ~]# df -h | awk 'NR==1{print $(NF-1),$NF}NR!=1{print $NF}'
Mounted on
/dev
/dev/shm
/run
/sys/fs/cgroup
/
/boot
/run/user/0
[root@kiwi222 ~]#
列如:
[root@kiwi222 ~]# cat txt1
kiwi
jkjk
qweqwr
111
[root@kiwi222 ~]#
## 还可以将字段分隔符定义为换行符
[root@kiwi222 ~]# awk 'BEGIN {FS="\n";RS=""}{print $1,$NF}' txt1
kiwi 111
[root@kiwi222 ~]#
8.关系操作符与布尔操作符
8.1关系操作符
操作符 | 描述 |
---|---|
< | 小于 |
> | 大于 |
<= | 小于或等于 |
>= | 大于或等于 |
== | 相等的 |
!= | 不等的 |
~ | 匹配 |
!- | 不匹配 |
[root@kiwi222 ~]# cat txt1
kiwi
jkjk
qweqwr
111
[root@kiwi222 ~]# awk 'NR==4 { print $0}' txt1
111
[root@kiwi222 ~]#
## 只有第四行才会被打印
## 打印除了第一行的
[root@kiwi222 ~]# awk 'NR>1 { print $0}' txt1
jkjk
qweqwr
111
[root@kiwi222 ~]#
8.2布尔操作符
操作符 | 定义 |
---|---|
|| | 逻辑或 |
&& | 逻辑与 |
! | 逻辑非 |
给定两个或多个表达式,只有当给定的表达式之一的值为真 (非零或非空)时,使用操作符 的等个表达式的值才为真。而只有当&&操作符连接的两个表达式的值都为真时结果才为真。
操作符! 表示都对其值取反
!(NR > 1 && NF > 3)
9.获取文件的信息
处理命令ls的输出:
[root@kiwi222 ~]# ls -l $* | awk '{ print $5, "\t",$9}'
20 111
1183 anaconda-ks.cfg
47 avg
269 kiwi
98 kiwi111
45 kiwi222
42 kiwi333
324 list
113 nameState
394 newlist
98 sedscr
22 txt1
[root@kiwi222 ~]#
## 打印文件的大小和名字。即打印第五个字段和第九个字段
## 打印每个文件的大小相加,得到列表的所有文件的总字节数
[root@kiwi222 ~]# ll | awk '{sum+=5;++filenum;print $5,"\t",$9}END{print "Total: ",sum}'
20 111
1183 anaconda-ks.cfg
47 avg
269 kiwi
98 kiwi111
45 kiwi222
42 kiwi333
324 list
113 nameState
394 newlist
98 sedscr
22 txt1
Total: 65
[root@kiwi222 ~]#
10.格式化打印
awk提供的printf可以代替print语句,printf是借用了C程序设计语言,它和print不一样。printf语句和print语句一样可以打印一个简单的字符串。但print的结果会自动换行,但printf不会。
[root@kiwi222 ~]# awk 'BEGIN{printf "hello world"}'
hello world[root@kiwi222 ~]# ^C
[root@kiwi222 ~]# awk 'BEGIN{print "hello world"}'
hello world
[root@kiwi222 ~]#
用在printf的格式说明符
字符 | 定义 |
---|---|
c | ASCII字符 |
d | 十进制整数 |
i | 十进制整数(在POSIX中增加的) |
e | 浮点格式([-]d.precisione[±]dd) |
E | 浮点格式([-]d.precisionE[±]dd) |
f | 浮点格式 ([-]ddd.precision) |
g | e或f的转换形式,长度最短,末尾的0被去掉 |
G | E或f的转换形式,长度最短,末尾的0被去掉 |
0 | 无符号的八进制 |
s | 字符串 |
u | 无符号的十进制 |
x | 无符号的十六进制,用a-f表示10-15 |
X | 无符号的十六进制,用A-F表示10-15 |
% | 字面字符% |
例如
[root@kiwi222 ~]# ll | awk 'NR!=1{print $5,"\t",$9}'
20 111
1183 anaconda-ks.cfg
47 avg
269 kiwi
98 kiwi111
45 kiwi222
42 kiwi333
324 list
113 nameState
394 newlist
98 sedscr
22 txt1
[root@kiwi222 ~]# ll | awk 'NR!=1{printf "%d\t%s\n", $5,$9}'
20 111
1183 anaconda-ks.cfg
47 avg
269 kiwi
98 kiwi111
45 kiwi222
42 kiwi333
324 list
113 nameState
394 newlist
98 sedscr
22 txt1
[root@kiwi222 ~]#
## %d表示字符串,%s表示数字
## 该语句输出$5 的值,后面是制表符\t 和$9、然后输出一个换行符。
将ls命令左对齐,并且算出总数
[root@kiwi222 ~]# ll | awk 'BEGIN{print "bytes","\t","file"}NR !=1{sum+=$5;++filenum;print $5,"\t",$9}END{printf "Total:\t %d bytes(%d file)\n",sum,filenum}'
bytes file
20 111
1183 anaconda-ks.cfg
47 avg
269 kiwi
98 kiwi111
45 kiwi222
42 kiwi333
324 list
113 nameState
394 newlist
98 sedscr
22 txt1
Total: 2655 bytes(12 file)
[root@kiwi222 ~]#