这里有必要先说下awk、sed、grep更适合的方向:
grep 更适合单纯的查找或匹配文本
sed 更适合编辑匹配到的文本
awk 更适合格式化文本,对文本进行较复杂格式处理
一、Awk简介
Awk是一种编程语言,用于在Linux/UNIX下对文本和数据进行扫描与处理,数据可以来自标准输入、文件、管道。Awk分别代表其作者姓氏的第一个字母,因为它的作者是三个人,分别是Alfred Aho、Peter Weinberger、Brian Kernighan。实际上,awk有很多种版本,如:awk、nawk、mawk、gawk、MKS awk、tawk等,这其中有开源产品,也有商业产品。目前在Linux中常用的awk编译器版本有mawk、gawk,其中以RedHat为代表使用的是gawk,以Ubuntu为代表使用的是mawk。gawk是GNU Project的awk解释器的开源代码实现。
二、Awk工作流程
Awk的工作流程是:逐行扫描文件,从第一行到最后一行,寻找匹配特定模式的行,并在这些行上进行用户想要的操作。Awk基本结构由模式匹配和处理过程(即处理动作)组成。
pattern {action}
Awk读取文件内容的每一行时,将对比该行是否与给定的模式相匹配,如果匹配,则执行处理过程,否则对该行不做任何处理。如果没有指定处理脚本,则把匹配的行显示到标准输出,即默认处理动作是print打印行;如果没有指定模式匹配,则默认匹配所有的数据。
Awk有两个特殊的模式:BEGIN和END,它们被放置在没有读取任何数据之前以及在所有的数据读取完成以后执行。
下图展示了Awk工作的整体流程,从图中可以看出,在读取文件内容前,BEGIN后面的指令将被执行,然后读取文件内容并判断是否与特定的模式匹配,如果匹配,则执行正常模式后面的动作指令,最后执行END模式命令,并输出文档处理后的结果。
也就是说:
BEGIN{ 这里面放的是执行前的语句 }
END { 这里面放的是处理完所有的行后要执行的语句 }
{ 这里面放的是处理每一行时要执行的语句 }
三、Awk基本语法格式
awk [选项参数] 'script' var=value file(s)
或
awk [选项参数] -f scriptfile var=value file(s)
选项参数说明:
-F fs or --field-separator fs
指定以 fs 输入文件折分隔符(默认分隔符为空格或制表符),fs是一个字符串或者是一个正则表达式,如-F。
-v var=value or --asign var=value
在执行处理过程以前,设置一个变量var值为value。
-f scripfile or --file scriptfile
从脚本文件中读取Awk指令,以取代在命令参数中输入处理脚本。
-W compat,-W traditional,--compat,--traditional
使用兼容模式运行Awk,GNU扩展选项将被忽略。
-W copyleft,-W copyright,--copyleft,--copyright
输出简短的GNU版权信息。
-W dump-variables[=file],--dump-variables[=file]
打印全局变量(变量名、类型、值)到文件中,如果没有提供文件名,则自动输出至名为dump-variables的文件中。
显示各个选项的简短描述。
awk程序的语法结构是:一个awk程序包含一系列的模式{动作指令}或是函数定义,模式可以是BEGIN、END、表达式,用来限定操作对象的多个表达式使用逗号分隔;动作指令需要以引起来。
示例如下:
(1)通过正则表达式/八S/匹配空白行,动作为打印Blank line,即文件如果有N个空白行,awk将在屏幕打印N个Blank line。
[root@192 ~]# awk '/^$/ {print "Blank line"}' test.txt
(2)打印包含主机名的行,没有指定动作指令,默认动作为打印。
[root@192 ~]# awk '/HOSTNAME/' test.txt
(3)提前编辑一个awk脚本,再通过-f选项调用该脚本。
[root@192 ~]# cat awk.sh
/^$/{print "Blank line"}
[root@192 ~]# awk -f awk.sh test.txt
注意:行匹配语句 awk '' 只能用单引号
四、Awk操作指令
1. 记录与字段
Awk一次从文件中读取一条记录,并将记录存储在字段变量$0中。记录被分割为字段并存储在$1,$2……,SNF中(默认使用空格或制表符为分隔符)。内建变量NF为记录的字段个数。
示例:
读取输入行并输出第一个字段、第二个字段、第三个字段。
[root@192 ~]# echo hello the world | awk '{print $1,$2,$3}'
hello the world
读取输入行并输出该行。
[root@192 ~]# echo hello the world | awk '{print $0}'
hello the world
读取输入行并输出该行的字段个数。
[root@192 ~]# echo hello the world | awk '{print NF}'
3
读取输入行并输出该行最后一个字段。
[root@192 ~]# echo hello the world | awk '{print $NF}'
world
2. 字段分隔符
默认Awk 读取数据以空格或制表符作为分隔符,但可以通过-F或FS(field separator)变量来改变分隔符。
[root@192 ~]# awk -F: '{print $1}' /etc/passwd
[root@192 ~]# awk 'BEGIN {FS=":"} {print $1}' /etc/passwd
注意,以上两个示例均将字段的分隔符改为冒号(:),即以冒号为分隔符打印passwd文件的第一个字段(账号名称)。如果使用FS改变分隔符,需要在BEGIN处定义FS,因为在读取第一行前,就需要改变字段分隔符。
进阶:指定多个字段分隔符(文档内容为:hello the:word,!)
[root@192 ~]# echo 'hello the:word,!' | awk 'BEGIN {FS="[:,]"} {print $1,$2,$3,$4}'
3. 内置变量
下表为Awk内置变量列表
变量名称 | 描述 |
$n | 当前记录的第n个字段,字段间由FS分隔 |
$0 | 完整的输入记录 |
ARGC | 命令行参数个数 |
ARGV | 数组,保存的是命令行所给定的各参数 |
FILENAME | 当前输入文档的名称 |
FNR | 当前输入文档的当前记录编号(各文件分别计数的行号),尤其是当有多个输入文档时有用 |
NR(Number of Record) | 行号,当前处理的文本行的行号 |
NF(Number for Field) | 当前行的字段个数 |
FS(Field Separator) | 字段分隔符 |
OFS(Out of Field Separator) | 输出字段分隔符,默认为空格 |
ORS(Output Record Separate) | 输出记录分隔符,默认为换行符\n |
RS(Record Separator) | 输入记录分隔符,默认为换行符\n |
实例:
[root@192 ~]# cat test1.txt
This is test file 1
Hello world1
[root@192 ~]# cat test2.txt
This is test file 2
Hello world2
Ask for more.
输出当前文档的当前行编号,第一个文件两行,第二个文件三行:
[root@192 ~]# awk '{print FNR}' test1.txt test2.txt
1
2
1
2
3
Awk将两个文档作为一个整体的输入流,通过NR输入当前行编号:
[root@192 ~]# awk '{print NR}' test1.txt test2.txt
1
2
3
4
5
文档的第一行有5个字段,第二行有2个字段
[root@192 ~]# awk '{print NF}' test1.txt
5
2
[root@192 ~]# awk '{print $1,$2,$3}' test1.txt
默认print输出时,各参数将输出分隔符默认为空格,所以输出内容如下:
This is test
Hello world1
下面通过OFS将输出分隔符设置为“-”,这个print在输出第一、二、三个字段时,中间的分隔符为“-”,结果如下:
[root@192 ~]# awk 'BEGIN {OFS="-"} {print $1,$2,$3}' test1.txt
This-is-test
Hello-world1-
4. 表达式与操作符
表达式由变量、常量、函数、正则表达式、操作符组成,Awk中变量有字符变量和数字变量。如果在Awk中定义的变量没有初始化,则初始值为空字串或0。注意,字符操作时一定记住需要加引号。变量定义示例:
a="welcome to beijing"
b=12
操作符(awk操作符与C语言类似)如下。
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 |
% | 取余 |
^ | 幂运算 |
++ | 自加1 |
-- | 自减1 |
+= | 相加后赋值给变量(x+=9等同于x=x+9) |
-= | 相减后赋值给变量(x-9等同于x=x-9) |
*= | 相乘后赋值给变量(x*9等同于x=x*9) |
/= | 相除后赋值给变量(x/=9等同于x=x/9) |
> | 大于 |
< | 小于 |
>= | 大于或等于 |
<= | 小于或等于 |
== | 等于 |
!= | 不等于 |
~ | 匹配正则表达式 |
!~ | 不匹配正则表达式 |
&& | 逻辑与 |
|| | 逻辑或 |
空格 | 连接 |
$ | 字段引用 |
in | 数组成员 |
操作符简单实例:
[root@192 ~]# echo |awk 'x=2 {print x+3}'
5
[root@192 ~]# echo "test" |awk 'x=2,y=3 {print x*2,y*3}'
4 9
[root@192 ~]# awk '/^$/ {print x+=1}' test.txt # 统计所有的空白行
[root@192 ~]# awk '/^$/ {x+=1} END {print x}' test.txt # 打印总空白行个数
[root@192 ~]# awk -F: '$1~/root/ {print $3}' /etc/passwd # 打印root的ID号
[root@192 ~]# awk -F: '$3>500 {print $1}' /etc/passwd # 列出计算机中ID号大于500的用户名
五、Awk高级应用
1. if 条件判断
if语法格式1:
if(表达式)
动作1
else
动作2
if语法格式2:
if(表达式) 动作1; else 动作2
如果表达式的判断结果为真,则执行动作1,否则执行动作2。
示例:判断 boot 分区可用容量小于20MB时报警,否则显示OK。
[root@192 ~]# df |grep "boot" |awk '{if($4<20000) print "Alert"; else print "OK"}'
2. while 循环
while语法格式1:
while(条件)
动作
语法格式示例如下:
x=1
while (i < 10) {
print $i
}
示例:
[root@192 ~]# awk 'i=1{} BEGIN {while(i<=10) {++i; print i}}' test.txt
while语法格式2:
do
动作
while(条件)
示例:
[root@192 ~]# awk 'BEGIN { do {++x; print x} while (x<=10)}'
3. for 循环
for(变量;条件;计数器)
动作
示例:
[root@192 ~]# awk 'BEGIN {for (i=1;i<=10;i++) print i}' test.txt
[root@192 ~]# awk 'BEGIN {for (i=10;i>=1;i--) print i}' test.txt
注意:因为以上循环语句使用的awk均使用BEGIN模式,也就是说,在未读取文档内容前就会将BEGIN代码执行完毕,所以输入文档可以为任意文档。
4. Break 与 Continue
break 跳出循环
continue 终止当前循环
示例(打印 1-4,6-10)
for (i=1; i<=10; i++){
if (i==5)
continue
print i
}
示例(仅打印1-4):
for (i=1; i<=10; i++){
if (i==5)
break
print i
}
六、函数
1. rand()函数
作用:产生0~1之间的浮点类型的随机数,rand产生随机数时需要通过srand)设置一个参数,否则单独的rand)每次产生的随机数都是一样的。
示例:
[root@192 ~]# awk 'BEGIN {print rand(); srand(); print srand()}' test.txt
2. gsub(x,y,z)函数
作用:在字串z中使用字串y替换与正则表达式x相匹配的所有字串,z默认为$0。
3. sub(x,y,z)函数
作用:在字串z中使用字串y替换与正则表达式x相匹配的第一个字串,z默认为$0。
将passwd每行中所有的root修改为admin显示至屏幕:
[root@192 ~]# awk -F: 'gsub(/root/, "admin",$0) {print $0}' /etc/passwd
将passwd每行中第一个root修改为admin显示至屏幕:
[root@192 ~]# awk -F: 'sub(/root/, "admin",$0) {print $0}' /etc/passwd
sub相当于sed中的s///,gsub相当于sed中的s///g。
4. length(z)函数
作用:计算并返回字串z的长度。
显示test.txt文档每行的字符长度:
[root@192 ~]# awk '{print length()}' test.txt
5. getline函数
作用:从输入中读取下一行内容。从下面df-h命令的输出结果可以看出,分区的剩余容量显示在第4列,但唯独/根分区的记录显示在了两行。我们需要判断当记录的字段个数为1时,读取下一行,并将该行的第3列显示至屏幕。
[root@192 ~]# df -h |awk '{if(NF==1) {getline; print $3}; if(NF==6) print $4}'
[root@192 ~]# df -h |awk 'BEGIN {print "Disk Free:"} {if(NF==1) {getline; print $3}; if(NF==6) print $4}'
参考自,丁明一 编著 《Linux运维之道》