【awk】学习笔记


【linux】目录贴
参考文章:awk命令详解
参考地址:awk 详解
参考数据:《Linux Shell核心编程指南》——丁明一

1. 概述

awk是专门为文本处理设计的编程语言,是一门数据驱动的编程语言,与sed类似都是以数据驱动的行处理软件,主要用于数据扫描、过滤、统计汇总工作,数据可以来自标准输入、管道或者文件。
awk在20世纪70年代诞生与贝尔实验室,其名称源于该软件三名开发者的姓氏,在 1985—1988 年被大量修订与重写并于 1988 年发布的Gnu awk,是目前应用最广泛的版本,在CentOS7 系统中默认使用的就是Gnu awk
awk有3个不同版本: awk、nawk和gawk,未作特别说明,一般指gawk,gawk 是 AWK 的 GNU 版本。

2. 基础语法

2.1 记录与字段

awk是一种处理文本文件的编程语言,文件的每行数据都被称为记录,默认以空格或制表符为分隔符,每条记录被分成若干字段(列),awk每次从文件中读取一条记录。

语法格式:

awk [选项] ‘条件{动作}  条件{动作} ... ...’  文件名

2.2 内置变量

awk 语法由一系列条件动作组成,在花括号内可以有多个动作,在多个动作之间使
分号分隔,在多个条件和动作之间可以有若干空格,也可以没有。awk 会逐行扫描以读
取文件内容,从第一行到最后一行,寻找与条件匹配的行,并对这些匹配的数据行执行特
定的动作。条件可以是正则匹配数字字符串比较,动作可以是打印需要过滤的数据或
者其他,如果没有指定条件则可以匹配所有数据行,如果没有指定动作则默认为 print 打印 操作。因为 awk 是逐行处理软件,所以这里的动作默认都隐含着循环,条件被匹配多少次,
动作就被执行多少次。awk 有很多内置变量,表 7-1 为常用的 awk 内置变量。
在这里插入图片描述

初始化文件素材代码
vim test1.txt#创建素材文件
hello the world!
Other men live to eat, while I eat to live.
It is never too late to mend.

vim test2.txt #创建素材文件
biscuit:crisp:chicken
salt-jam::oil,sugar
banana:lemon,pear--apple:grape
free | awk '{print $2}' #逐行打印第 2 列

在这里插入图片描述

在这里插入图片描述
>

free 命令输出的内容有 3 行,而 awk 是逐行处理工具,当读取第 1 行数据时,因为 awk
没有指定匹配条件,可以匹配所有数据行,而条件匹配成功则执行 print $2 这个动作(打印
第 2 列数据),因此屏幕输出的内容为 used,接着读取第 2 行数据,再次执行 print $2 打印
第 2 行的第 2 列数据,因此屏幕输出的内容是 2031888,最后读取第 3 行数据,执行 print $2
打印第 3 行的第 2 列数据,所以屏幕输出的内容是 2097148。
由此可知,动作指令 print $2 虽然只写了一次,但是 awk 隐含了循环,条件匹配多少次, 动作就会被执行多少次。上面的案例因为数据源有 3 行,因此打印第 2 列的动作也被重复
执行了 3 次

free | awk '{print NR}'#输出行号 Number of Records
free | awk '{print NF}'#输出每行数据的列数 Number of Fields
awk '{print $2}' test1.txt  #打印文件每行第 2 列
awk '{print $3}' test1.txt  #打印文件每行第 3 列
awk '{print $0}' test1.txt  #打印每行全部内容
awk '{print}' test1.txt #打印每行全部内容******************
awk '{print NR}' test1.txt #打印当前行行号
awk '{print NR}' test1.txt test2.txt #2个文件按顺序合并,并打印当前行行号
awk '{print FNR}' test1.txt test2.txt #分别打印当前文档的行号,不合并
#同样都是输出行号,内置变量 NR 会将所有文件的数据视为一个数据流,NR 仅保存的是这些数据流的递增行号,而 FNR 则是将多个文件的数据视为独立的若干个数据流,遇到新文件时行号会从 1 开始重新递增,也就是 FNR 保存的是某行数据在源文件中的行号。
awk '{print NF}' test1.txt #打印每行数据的列数 
awk '{print $NF}' test1.txt #打印每行的最后 1 列  (假设数据行有 7 列,即 NF=7,因此$NF 也就是$7(第 7 列))。
awk '{print $(NF-1)}' test1.txt #打印每行倒数第 2 列 (假设数据行有 7 列,即 NF=7,因此$(NF-1) 也就是$(7-1)(第 6 列))
awk '{print $(NF-2)}' test1.txt #打印每行倒数第 3 列
awk '{print FILENAME}' test1.txt #打印文件名 为什么输出了 多次次文件名呢?因为 awk 是逐行处理软件,满足条件则执行动作指令,上面的命令在动作指令前面没有编写条件,则默认匹配所有,而 test1.txt 文件有 3 行数据,因此屏幕最终输出了 3 次文件名信息。

在这里插入图片描述
在这里插入图片描述

2.3 自定义变量

awk 可以通过-v(variable)选项设置或者修改变量的值,我们可以使用-v 定义新的变量,也可以使用该选项修改内置变量的值。

awk -v x="Jacob" -v y=11 '{print x,y}' test1.txt #定义变量,输出变量值

# 有时编写 awk 命令需要系统变量的值,我们也可以通过-v 选项或者组合多个引号实现这样的功能。
x="hello" #自定义系统变量
awk -v i=$x '{print i}' test1.txt#awk 调用系统变量一(-v方式)
awk '{print "'$i'"}' test1.txt#awk 调用系统变量二(多个引号组合) 这条命令在变量$i 外面是双引号加单引号的组合。
awk -v FS=":" '{print $1}' test2.txt #重新定义分隔符为冒号
awk -F ":" '{print $1}' test2.txt #重新定义分隔符为冒号


# 可以使用[]定义分隔符集合,同时设置多个分隔符。比如使用[:,-]表示以冒号(:)、逗号(,)或者横线(-)为分隔符。在[]集合中的横线,放在中间位置表示范围区间,比如[a-z]。而当我们需要的是普通字符-时,就需要将其放在最前或者最后面,比如[-abc]或者[abc-]。
awk -v FS="[:,-]" '{print $1}' test2.txt
awk -v FS="[:,-]" '{print $2}' test2.txt
#因为自定义数据行字段的分隔符属于经常使用的功能,为了方便自定义字段分隔符,awk 程序还替换了一个-F 选项,可以直接指定数据字段的分隔符。
awk -F: '{print $1}' test2.txt#定义冒号为字段分隔符(F和符号间可以有空格分开)
awk -F"[:,-]" '{print $2}' test2.txt #使用集合定义分隔符


#内置变量 RS 保存的是输入数据的记录分隔符,也就是行分隔符,默认值为\n 换行符。通过修改 RS 的值同样可以指定其他字符为记录分隔符。
awk -v RS="," '{print $1}' test1.txt #定义以逗号为行分隔符
hello
while
#在我们自定义使用逗号作为记录行的分隔符后,hello the world!\nOther men live to eat就被系统识别为了文件的第 1 行数据,而 while I eat to live.\nIt is never too late to mend,则被识别为文件的第 2 行数据,因此当使用 print $1 输出每行第一列数据时,其结果分别为 hello和 while。


#内置变量 OFS 保存的是输出字段的分隔符,默认为空格,而变量 ORS 保存的是输出记录的分隔符,默认为换行符\n。这些内置变量也都可以使用-v 选项来自定义修改。
awk '{print $3,$1,$3}' test1.txt#默认字段分隔符为空格
awk -v OFS=":" '{print $3,$1,$3}' test1.txt #定义字段分隔符为冒号
awk -v OFS="-" '{print $3,$1,$3}' test1.txt
#默认输出字段(列)之间使用空格作为分隔符,但是我们也可以修改 OFS 的值为其他字符,比如冒号、横线或者 Tab 制表符。
awk -v OFS=". " '{print NR,$0}' test1.txt
#上面的命令通过修改 OFS 定义输出分隔符为点和空格,这样后面在逐行读取数据并打印 NR(行号)和$0(全行所有数据内容)时,两个数据记录之间的分隔符就是点和空格。

awk -v ORS=":" '{print}' test1.txt #自定义行分隔符为冒号,awk 为逐行处理软件,默认在读取第 1 行数据并输出该行内容后,会自动在其后追加一个\n 换行符,接着处理后续的其他数据行。但如果我们修改了 ORS 变量的值,也可以将行的分隔符(记录分隔符)修改为其他字符,比如冒号(:),上面的命令通过-v 参数修改 ORS 变量的值为冒号。

2.4 print 指令

使用 print 指令输出特定数据时,我们可以输出变量数据,同时也还可以直接输出常量,
如果是字符串常量需要使用双引号括起来,如果是数字常量则可以直接打印。

awk '{print "CPU"}' test1.txt
awk '{print "data:",$1}' test1.txt
awk '{print 12345}' test1.txt
awk '{print $1,12345,$3}' test1.txt

2.5 条件匹配

前面的案例都没有编写条件匹配,awk 支持使用正则进行模糊匹配,也支持字符串
数字的精确匹配,并且支持逻辑与逻辑或。awk 比较符号如表 7-2 所示。

在这里插入图片描述

awk '/world/{print}' test1.txt #打印包含 world 的行
awk '/world/' test1.txt #打印包含 world 的行

上面两条命令都是输出包含 world 的数据行,当没有动作指令时则默认指令是 print 打印当前行所有数据内容。awk 会逐行读取所有数据,对每行数据都进行正则匹配,匹配到
包含 word 的数据行时则打印全部所有内容。awk 数据处理流程如图 7-2 所示。

在这里插入图片描述

默认正则匹配是对每行所有数据内容进行匹配,但是 awk 支持仅对某列进行正则匹配,在两个数据之间进行正则匹配需要使用正则比较符(~)进行匹配比较。

 awk '$2~/the/' test1.txt #每行第 2 列正则匹配 the
 awk '$3~/never/{print $1,$4,$5}' test1.txt #读取 test1.txt 文件的每行数据,逐行匹配第 3 列是否包含 never 关键词,如果包含则打印输出该行的第 1 列、第 4 列及第 5 列。
 awk '$4~/to/' test1.txt #第 4 列正则匹配包含 to 的行
 awk '$4=="to"' test1.txt #第 4 列精确匹配 to
 awk '$2!="the"' test1.txt #第 2 列不等于 the

 head -2 /etc/passwd #查看素材文件内容
 awk -F: '$3<=10' /etc/passwd #匹配第 3 列小于 10 的行
 awk -F: '$3>=100' /etc/passwd #匹配第 3 列大于等于 100 的行
 awk -F: '$3<=10{print $1}' /etc/passwd #逐行精确匹配/etc/passwd 文件的第 3 列,如果第三列的数字小于等于 10则打印该行第 1 列的数据内容。
 awk -F: '$3>=100' /etc/passwd #匹配第 3 列大于等于 100 的行
 awk 'NR==4' /etc/passwd  #仅显示第 4 行数据内容
 awk -F: '$3>1&&$3<5' /etc/passwd #逻辑与,满足两个条件
 awk -F: '$3==1||$3==5' /etc/passwd #逻辑或,满足两个条件之一


#awk 的匹配条件可以是 BEGIN 或 END(大写字母),BEGIN 会导致动作指令仅在读取任何数据记录之
#前执行一次,END 会导致动作指令仅在读取完所有数据记录后执行一次。利用 BEGIN 我们可以进行数
#据的初始化操作,而 END 则可以帮助我们进行数据的汇总操作
awk 'BEGIN{print "OK"}'
awk 'BEGIN{print "OK"}' /etc/passwd  `只会输出一行OK`#BEGIN 后面的动作指令,在读取任何数据记录前就被执行且仅执行一次,因此上面的指令不需要通过文件读取任何数据即可执行(`END则不行`),如果添加了文件也没有任何影响。
awk 'END{print NR}' /etc/passwd
#END 后面的动作指令,仅在读取完所有数据流之后被执行一次,NR 变量的值为当前
#行的行号,读取第 1 行数据时 NR 的值为 1,读取第 2 行数据时 NR 的值为 2,依此类推。
#因为 END 的指令在读取完所有数据行之后才会执行,当读取完所有数据行后 NR 的值为
#/etc/passwd 文件最后一行的行号,此时再打印输出 18,表示/etc/passwd 文件有 18 行。

#awk 还可以通过算术运算符进行数字计算。
awk 'BEGIN{print 2+3}'  #加法运算
awk 'BEGIN{print 10-4}' #减法运算
awk 'BEGIN{print 2*3}' #乘法运算
awk 'BEGIN{print 2/10}' #除法运算
awk 'BEGIN{print 6%3}' #取余运算
awk 'BEGIN{print 2**3}' #幂运算
awk 'BEGIN{x=8;y=2;print x-y}' #对变量进行减法运算
awk 'BEGIN{x=8;y=2;print x*y}' #对变量进行乘法运算
awk 'BEGIN{x=1;x++;print x}' #x=x+1
awk 'BEGIN{x=1;x--;print x}' #x=x-1
awk 'BEGIN{x=1;x+=8;print x}' #x=x+8
awk 'BEGIN{x=8;x-=2;print x}' #x=x-2
awk 'BEGIN{x=8;x*=2;print x}' #x=x*2
awk 'BEGIN{x=8;x/=2;print x}' #x=x/2
awk 'BEGIN{x=8;x%=2;print x}' #x=x%2
#在 awk 中变量不需要定义就可以直接使用,作为字符处理时未定义的变量默认值为空,
#作为数字处理时未定义的变量默认值为 0。
awk 'BEGIN{print x+8}' #print 0+8
awk 'BEGIN{print x*8}'  #print 0*8
awk 'BEGIN{print "["x"]","["y"]"}' #x 和 y 变量默认为空 输出:[] []
#使用双引号引用的[和]被识别为常量字符串,输入什么即打印什么,x 和 y 没有引号则被识别为变量,没有任何计算操作,awk 将其识别为字符型变量,输出结果为空,最终打印两个方括号[],方括号中间为空。
awk '/bash$/{x++} END{print x}' /etc/passwd  #逐行读取/etc/passwd 文件,x 初始值为 0,匹配以 bash 结尾的行时执行 x++,读取完所有数据行后打印 x 的值。如果第 1 行以 bash 结尾则 x++=1,如果第 2 行数据不以 bash 结尾则跳过,依此类推。上面的命令最终输出 2 表示系统中有两个账户的解释器为 bash。
 
who #查看当前登录信息
who | awk '$1=="root"{x++} END{print x}' #统计root 登录次数
seq 200 | awk '$1%7==0 && $1~/7/' #打印 1~200 之间所有能被 7 整除并且包含 7 的整数数字。首先将第 1 列对 7 进行取余计算($1%7),然后判断取余的值是否等于 0,等于 0 表示可以整除,否则无法整除,最后对第 1 列进行正则匹配,看它是否包含 7。

ls -l /etc | awk '/^-/{sum+=$5} END{print "文件总容量:"sum"."}'
#ls -l 命令的输出结果中以-开头的行代表普通文件,以 d 开头的行代表目录,以 l 开头的
#行代表链接文件,以b 开头的行代表块设备文件(如磁盘、光盘等),以 c 开头的行代表
#字符设备文件(如鼠标、键盘等)。

在这里插入图片描述

3. awk 条件判断

在使用 awk 进行数据的基本过滤和统计工作,而 awk 作为一门数据驱动的编程语言,功能远不止于此,它有自己的循环和判断语句,并且支持函数等更高级
的功能。下面我们先来看看 awk 的 if 条件判断语句。
与其他所有语言一样,awk 的 if 判断语句同样支持单分支双分支多分支判断。

awk 的单分支 if 判断语法格式如下,if 判断后面如果只有一个动作指令,则花括号{}可以省略,如果 if 判断后面的指令为多条指令则需要使用花括号{}括起来,多个指令使用分号分隔。(尽量每条动作指令序列都用分号结束,单条指令不用分号结束不会出错,如果是在多个动作/条件中间+不用大括号,就会报错.如 {if ($3>2) x++ else y++},x++会报语法错误,而y++不会,如果x++;则正确.)

#单分支
if(判断条件){
动作指令序列;
}
#双分支
if(判断条件){
动作指令序列 1;
}
else{
动作指令序列 2;
}
#多分支
if(判断条件 1){
动作指令序列 1;
}
else if(判断条件 2){
动作指令序列 2}
... ...
else{
动作指令序列 N;
}

if 属于判断指令,而在 awk 中所有的动作指令都必须写在{}中。

ps -eo user,pid,pcpu,comm #查看进程列表信息
ps -eo user,pid,pcpu,comm | awk '{ if($3>0.5) {print} }' #通过 awk 的 if 判断,我们可以将第 3 列大于 0.5 的数据行匹配出来,找到满足该条件的行后使用 print 打印该行的全部内容。这样就可以快速找出占用 CPU 较多的进程
ps -eo user,pid,rss,comm | awk '{ if($3>1024) {print} }' #ps 命令输出 rss 信息可以查看进程占用的内存容量信息,有了这些信息就可以快速找出占用内存较多的进程列表,上面的命令是找出占用内存大于 1024KB 的进程列表。

##统计用户数量
useradd rick #创建两个普通用户
useradd vicky
awk -F: \
'{if($3<1000){x++}else{y++}} \
END{print "系统用户个数:"x,"普通用户个数:"y}' /etc/passwd

## 统计不同文件数量
ls -l /etc | awk \
'{
if($1~/^-/) {x++} else {y++} \
} \
END {print "普通文件个数:"x,"目录个数:"y}'

##统计分数
awk '{if($2>90){print $1,"\t 完美人生";} else if($2>=70){print $1,"\t良好中庸"}}' test.txt

4. awk数组与循环

4.1 数组

awk 支持关联数组,数组的索引下标可以不是连续的数字,索引下标可以是任意字符数字,当使用数字作为索引时 awk 会自动将数字转换为字符,如果直接使用字符作索引
则需要使用引号括起来。(数组索引为字符)
直接使用数组名加索引下标即可调用数组的值。因为数字索引会被自动转换为字符, 所以在定义数组使用数字的情况下,调用数组时也可以使用字符的形式调用。

一维数组:
数组名[索引]=值
多维数组:
数组名[索引 1][索引 2]=值
或者
数组名[索引 1,索引 2]=值

awk 'BEGIN{a[0]=11;print a[0]}'#定义数组,调用数组
awk 'BEGIN{a[0]=88;print a["0"]}'
awk 'BEGIN{ tom["age"]=22;  tom["addr"]="beijing"; print tom["age"],tom["addr"] }'
awk 'BEGIN{ a["a"]["a"]=11;a["a"]["b"]=22; print a["a"]["b"],a["a"]["a"] }' #创建关联二维数组
awk 'BEGIN{ a[0,1]=11;a[0,8]=88;a[1,3]=44;print a[0,8],a[1,3]}' #创建关联二维数组

如果数组有多个元素,逐行读取数组元素比较麻烦,我们可以使用 for 循环获取数组元素的索引下标,然后在循环体内将数组元素取出,其语法格式如下。for 循环后面的指令如果只有一个,则花括号{}可以省略。

for(变量 in 数组名) { 动作指令序列 }

awk 'BEGIN{a[10]=11;a[88]=22; for(i in a ) print i}' #这种通过循环的方式输出数组索引或者元素值时并不会按照输入的顺序输出,它是无序的。
awk 'BEGIN{a[10]=11;a["88a"]=22; for(i in a ) print i,"\t"a[i]}'

#结合 awk 的 if 语句,我们还可以进行成员关系判断,判断一个索引是否存在为数组成员,语法格式如下。
awk 'BEGIN{a[10]=11;a["88a"]=22; if(22 in a ) print "YES";else print "NO";}' #NO

awk 'BEGIN{a[10]=11;a["88a"]=22; if("88a" in a ) print "YES";else print "NO";}' #YES

df |awk 'NR!=1 {disk[$1]=$4} END{for(i in disk) print i,"\t"disk[i];}' |column -t #awk 会逐行读取 df 输出的数据行,并定义数组 disk,将每行第 1 列作为数组的索引下标,第 4 列作为数组元素的值,NR!=1 可以防止将第 1 行标题行写入数组。等所有数据行都读取完毕后,再通过 for 循环将 disk 数组的所有元素值逐一打印出来。


4.2 for循环

awk 的 for 循环采用与 C 语言一样的语法格式,具体格式如下。
for(表达式 1;表达式 2;表达式 3) { 动作指令序列 }

awk 'BEGIN{for(i=0;i<=4;i++) print i;}' 
awk '{for (i=1;i<=NF;i++) {if($i~/apple/) x++;}} END{print x}' test4.txt #统计 apple 在文件中出现的次数。

下面结合脚本,编写一个定义特定数据坐标的脚本,用户输入一个关键词,脚本就可以反馈该关键词在文件中的行和列的坐标。

#!/bin/bash
#功能描述(Description):给定关键词,脚本输出该关键词在文件中的行与列坐标
echo "入参:"$1
key=$1
#awk '{print 'key'}' test4.txt
awk '{for(i=1;i<=NF;i++){if ($i~/'$key'/) print "'$key'的坐标为:"NR"行",i"列"}}' $2
# 条件中'$key' 无需添加双引号,print打印需要添加双引号,说明:/apple/匹配的本身就是字符串, print "apple" 才会打印字符串

[root@cyjcentos7-6 awk]# sh localtioin.sh apple test4.txt
入参:apple
apple的坐标为:1行 4列
apple的坐标为:1行 8列
apple的坐标为:2行 2列
apple的坐标为:3行 3列

4.3 while循环

 awk 'BEGIN{ i=1; while(i<=5) {print i;i++} }'

4.4 中断循环

与 Shell 脚本类似 awk 提供了 continue、break 和 exit 循环中断语句,方便我们在特定环境下对循环进行中断操作。continue 可以中断本次循环加入下一次循环,break 中断整个循环体,exit 可以中断整个 awk 动作指令,直接跳到 END 的位置。这些指令既可以中断 for
循环,也可以中断 while 循环。

wk 'BEGIN{i=0; while (i<=5){i++;if(i==3)continue;print i} print "over"} END{print "END"}'  test4.txt

#break 比 continue 中断得更彻底,它可以把整个循环中断,但是对循环体外部的命令也没有任何影响,这次屏幕在显示完数字 1 和 2 后,就中断了整个 while 循环,但是依然可以执行 print over 和 print end。

#使用 exit 不仅可以中断循环,还会中断 awk 其他所有动作指令,直接跳到并执行 END的指令。因此上面的命令在循环两次输出 1 和 2 后,直接结束 awk 指令,但是不影响 END中指令的执行,屏幕依然显示 end。

4. awk函数

awk 内置了大量的函数可供我们直接调用实现更丰富的功能,同时还允许自定义函数。下面为大家介绍一些常用的内置函数,以及如何编写自定义函数

4.1 内置 I/O 函数

getline 函数可以让 awk 立刻读取下一行数据(读取下一条记录并复制给$0,并重新设
置 NF、NR 和 FNR)。

 df -h | awk '{ if(NF==1){getline;print $3} ; if(NF==6) {print $4} }'

在这里插入图片描述
在这里插入图片描述

next 函数可以停止处理当前的输入记录,立刻读取下一条记录并返回 awk 程序的第一个模式匹配重新处理数据。getline 函数仅仅读取下一条数据,而不会影响后续 awk 指令的
执行。但是 next 不仅读取下一行数据,会导致后续的指令都不再执行,而是重新读取数据后重新回到 awk 指令的开始位置,重新匹配,重新执行动作指令。

vim test.txt
##########
noraml line
next line: I like to read and draw.
noraml line
noraml line
##############

awk '/air/{getline;print "next line:",$0} {print "noraml
line"}' test.txt
awk '/air/{next;print "next line:",$0} {print "noraml
line"}' test.txt
#对比上面两条命令的区别,对于无法匹配正则条件/air/的行都不会执行 getline 或 next 指令,都执行 print "noraml line"。对于匹配正则条件/air/的行,如果执行 getline 函数则并不影响后续的 print "next line:",$0 指令的执行,如果执行的是 next 函数,则通过最终的输出结果可知next 后续的 print 指令都不再执行,而是跳回 awk 的开始处重新匹配条件执行动作指令。

system(命令)函数可以让我们在 awk 中直接调用 Shell 命令。awk 会启动一个新的 Shell进程执行命令。

awk 'BEGIN{system("ls")}'

i=ls
awk 'BEGIN{system("'$i'")}'

awk 'BEGIN{system("echo test")}'

awk '{system("echo 'date:'"$0)}' test6.txt
awk '{system("echo date:"$0 " >> /tmp/test")}' test.txt

4.2 内置函数

cos(expr)函数返回 expr 的 cosine 值。(注意:括号内不是角度)
sin (expr)函数返回 expr 的 sine 值。(注意:括号内不是角度)
sqrt(expr)函数返回 expr 的平方根。
int(expr)函数为取整函数,仅截取整数部分数值。
rand()函数可以返回 0 到 1 之间的随机数 N(0<=N<1)
srand([expr])函数可以使用 expr 定义新的随机数种子,没有 expr 时则使用当前系统的时
间为随机数种子。

awk 'BEGIN{print cos(30)}'
awk 'BEGIN{print sin(90)}'
awk 'BEGIN{print sqrt(2)}'
awk 'BEGIN{print int(6.8)}' #6
awk 'BEGIN{print int(6.1)}' #6
awk 'BEGIN{print rand()}' #0.237788
awk 'BEGIN{for(i=1;i<=5;i++)print rand()}'

awk 'BEGIN{print rand()}' #0.237788 没有新种子随机数固定
awk 'BEGIN{print rand()}' #0.237788 
awk 'BEGIN{srand();print rand()}' # 0.325548 使用时间做随机数种子
awk 'BEGIN{srand();print rand()}' # 0.787211 使用时间做随机数种子
awk 'BEGIN{srand(777);print rand()}' #0.478592 使用数值做随机数种子
awk 'BEGIN{srand(777);print rand()}' #0.478592
awk 'BEGIN{srand(888);print rand()}' # 0.850364

4.3 内置字符串函数

length([s])函数可以统计字符串 s 的长度,如果不指定字符串 s 则统计$0 的长度。
index(字符串 1,字符串 2)函数返回字符串 2 在字符串 1 中的位置坐标。
match(s,r)函数根据正则表达式 r 返回其在字符串 s 中的位置坐标。
tolower(str)函数可以将字符串转换为小写。
toupper(str)函数可以将字符串转换为大写。
split(字符串,数组,分隔符)函数可以将字串按特定的分隔符切片后存储在数组中,如果没有指定分隔符,则使用 FS 定义的分隔符进行字符串切割。
gsub(r,s,[,t])函数可以将字符串 t 中所有与正则表达式 r 匹配的字符串全部替换为 s,果没有指定字符串 t,默认对$0 进行替换操作。(既可以用双引号,也可用// ,如果匹配特殊字符,eg: "\"" 表示匹配双引号, 而 /"/ 匹配双引号)
sub(r,s,[,t])函数与 gsub 类似,但仅替换第一个匹配的字符串,而不是替换全部。

awk 'BEGIN{test="hello the world";print length(test)}' #15
awk 'BEGIN{t[0]="hi";t[1]="the";t[2]="world";print length(t)}' # 3 返回数组元素个数(长度)
awk 'BEGIN{t[0]="hi";t[1]="the";t[2]="world";print length(t[0])}' #2  #返回 t[0]值的长度
awk 'BEGIN{t[0]="hi";t[1]="the";t[2]="world";print length(t[1])}' #3  #返回 t[1]值的长度
awk 'BEGIN{t[0]="hi";t[1]="the";t[2]="world";print length(t[2])}' #5  #返回 t[2]值的长度
awk '{print length()}' /etc/shells #返回文件每行的字符长度

awk 'BEGIN{test="hello the world";print index(test,"h")}' #1 #h 在 test 变量的第 1 个位置
awk 'BEGIN{test="hello the world";print index(test,"t")}' #7 #t 在 test 变量的第 7 个位置

awk 'BEGIN{print match("How much? 981$","[0-9]")}' #11 #数字在第 11 个位置出现.
awk 'BEGIN{print match("How much? 981$","[a-z]")}' #2 

awk 'BEGIN{print tolower("THIS IS A TEst")}'
awk 'BEGIN{apple="ReD APPle";print tolower(apple)}'
awk 'BEGIN{apple[0]="ReD APPle";print tolower(apple[0])}'

awk 'BEGIN{split("hello the world",test);print test[3],test[2],test[1]}'
awk 'BEGIN{split("hello:the:world",test,":");print test[1],test[3]}' ##自定义分隔符为冒号
awk 'BEGIN{split("hi8the9world3!",test,"[0-9]");print test[3],test[4]}' ##使用正则定义分隔符
 
awk 'BEGIN{hi="hello world";gsub("o","O",hi);print hi}' #hellO wOrld  所有 o 替换为 O
awk 'BEGIN{hi="Hello World";gsub("[a-z]","*",hi);print hi}'  #H**** W****  #所有小写字母替换为星
awk 'BEGIN{hi="Hello World";gsub(/[a-z]/,"*",hi);print hi}'  #H**** W****  #所有小写字母替换为星

head -1 /etc/passwd | awk '{sub("[0-9]","**");print $0}'
# root:x:**:0:root:/root:/bin/bash 
head -1 /etc/passwd | awk '{sub("root","XX");print $0}' # XX:x:0:0:root:/root:/bin/bash
awk 'BEGIN{hi="Hello World";sub("[A-Z]","*",hi);print
hi}' #*ello World

awk 'BEGIN{hi="Hello World";print substr(hi,2,3)}' #ell 从第 2 位开始截取 3 个字符
awk 'BEGIN{hi="Hello World";print substr(hi,2)}' #ello World #从第 2 位开始截取到末尾

4.4 内置时间函数

systemtime()返回当前时间距离 1970-01-01 00:00:00 有多少秒。

awk 'BEGIN{print systime()}'

4.5 用户自定义函数

awk 用户自定义函数格式如下。
function 函数名(参数列表) { 命令序列 }

awk 'function myfun(){print "hello myfun"} BEGIN{myfun();}'
awk 'function max(x,y){if(x>y) return x;else return y;} BEGIN{print max(9,18);}'

5. 实战案例:awk 版网站日志分析

awk '{IP[$1]++} END{for(a in IP) print a,IP[a];}' nginx2.log  |column -t # 统计IP访问次数

awk -F"[: /]" \
'$7":"$8>="10:00"&&$7":"$8<="10:30"' /usr/local/nginx/logs/access.log #按照时间过滤

6. 实战案例:监控网络连接状态

vim netstat.sh

#!/bin/bash
#功能描述(Description):监控网络连接状态脚本
#所有 TCP 连接的个数
TCP_Total=$(ss -s | awk '$1=="TCP"{print $2}')
#所有 UDP 连接的个数
UDP_Total=$(ss -s | awk '$1=="UDP"{print $2}')
#所有 UNIX sockets 连接个数
Unix_sockets_Total=$(ss -ax | awk 'BEGIN{count=0} {count++} END{print
count}')
#所有处于 Listen 监听状态的 TCP 端口个数
TCP_Listen_Total=$(ss -antlpH | awk 'BEGIN{count=0} {count++} END{print count}')
#所有处于 ESTABLISHED 状态的 TCP 连接个数
TCP_Estab_Total=$(ss -antpH | awk 'BEGIN{count=0} /^ESTAB/{count++} END{print count}')
#所有处于 SYN-RECV 状态的 TCP 连接个数
TCP_SYN_RECV_Total=$(ss -antpH | awk 'BEGIN{count=0} /^SYN-RECV/{count++} END{print count}')
#所有处于 TIME-WAIT 状态的 TCP 连接个数
TCP_TIME_WAIT_Total=$(ss -antpH | awk 'BEGIN{count=0} /^TIME-WAIT/{count++} END{print count}')
#所有处于 TIME-WAIT1 状态的 TCP 连接个数
TCP_TIME_WAIT1_Total=$(ss -antpH | awk 'BEGIN{count=0} /^TIME-WAIT1/{count++} END{print count}')
#所有处于 TIME-WAIT2 状态的 TCP 连接个数
TCP_TIME_WAIT2_Total=$(ss -antpH | awk 'BEGIN{count=0} /^TIME-WAIT2/{count++} END{print count}')
#所有远程主机的 TCP 连接次数
TCP_Remote_Count=$(ss -antH | awk '$1!~/LISTEN/{IP[$5]++} END{ for(i in IP){print IP[i],i} }' | sort -nr)
#每个端口被访问的次数
TCP_Port_Count=$(ss -antH | sed -r 's/ +/ /g' | awk -F"[ :]" '$1!~/LISTEN/{port[$5]++} END{for(i in port){print port[i],i}}' | sort -nr)
#定义输出颜色
SUCCESS="echo -en \\033[1;32m" #绿色
NORMAL="echo -en \\033[0;39m" #黑色
#显示 TCP 连接总数
tcp_total(){
echo -n "TCP 连接总数: "
$SUCCESS
echo "$TCP_Total"
$NORMAL
}
#显示处于 LISTEN 状态的 TCP 端口个数
tcp_listen(){
echo -n "处于 LISTEN 状态的 TCP 端口个数: "
$SUCCESS
echo "$TCP_Listen_Total"
$NORMAL
}
#显示处于 ESTABLISHED 状态的 TCP 连接个数
tcp_estab(){
echo -n "处于 ESTAB 状态的 TCP 连接个数: "
$SUCCESS
echo "$TCP_Estab_Total"
$NORMAL
}
#显示处于 SYN-RECV 状态的 TCP 连接个数
tcp_syn_recv(){
echo -n "处于 SYN-RECV 状态的 TCP 连接个数: "
$SUCCESS
echo "$TCP_SYN_RECV_Total"
$NORMAL
}
#显示处于 TIME-WAIT 状态的 TCP 连接个数
tcp_time_wait(){
echo -n "处于 TIME-WAIT 状态的 TCP 连接个数: "
$SUCCESS
echo "$TCP_TIME_WAIT_Total"
$NORMAL
}
#显示处于 TIME-WAIT1 状态的 TCP 连接个数
tcp_time_wait1(){
echo -n "处于 TIME-WAIT1 状态的 TCP 连接个数: "
$SUCCESS
echo "$TCP_TIME_WAIT1_Total"
$NORMAL
}
#显示处于 TIME-WAIT2 状态的 TCP 连接个数
tcp_time_wait2(){
echo -n "处于 TIME-WAIT2 状态的 TCP 连接个数: "
$SUCCESS
echo "$TCP_TIME_WAIT2_Total"
$NORMAL
}
#显示 UDP 连接总数
udp_total(){
echo -n "UDP 连接总数: "
$SUCCESS
echo "$UDP_Total"
$NORMAL
}
#显示 UNIX sockets 连接总数
unix_total(){
echo -n "Unix sockets 连接总数: "
$SUCCESS
echo "$Unix_sockets_Total"
$NORMAL
}
#显示每个远程主机的访问次数
remote_count(){
echo "每个远程主机与本机的并发连接数: "
$SUCCESS
echo "$TCP_Remote_Count"
$NORMAL
}
#显示每个端口的并发连接数
port_count(){
echo "每个端口的并发连接数: "
$SUCCESS
echo "$TCP_Port_Count"
$NORMAL
}
print_info(){
echo -e "------------------------------------------------------"
$1
}
print_info tcp_total
print_info tcp_listen
print_info tcp_estab
print_info tcp_syn_recv
print_info tcp_time_wait
print_info tcp_time_wait1
print_info tcp_time_wait2
print_info udp_total
print_info unix_total
print_info remote_count
print_info port_count
echo -e "------------------------------------------------------"

7.实战案例:获取 SSH 暴力破解攻击黑名单列表

8. 实战案例:性能监控脚本

#!/bin/bash
#功能描述(Description):监控服务器主要性能参数指标
#监控项目:内核信息、主机名称、IP 地址、登录账户、内存与 swap 信息、磁盘信息、CPU 负载
kernel=$(uname -r) #内核信息
release=$(cat /etc/redhat-release) #操作系统版本
hostname=$HOSTNAME #主机名称
localip=$(ip a s | awk '/inet /{print $2}') #本地 IP 地址列表
mem_total=$(free | awk '/Mem/{print $2}') #总内存容量
mem_free=$(free | awk '/Mem/{print $NF}') #剩余内存容量
swap_total=$(free | awk '/Swap/{print $2}') #总 swap 容量
swap_free=$(free | awk '/Swap/{print $NF}') #剩余 swap 容量
disk=$(df | awk '/^\/dev/{print $1,$2,$4}'|column -t) #磁盘信息
load1=$(uptime | sed 's/,//g' | awk '{print $(NF-2)}') #CPU 最近 1 分钟的平均负载
load5=$(uptime | sed 's/,//g' | awk '{print $(NF-1)}') #CPU 最近 5 分钟的平均负载
load15=$(uptime | sed 's/,//g' | awk '{print $NF}') #CPU 最近 15 分钟的平均负载
login_users=$(who | wc -l) #登录用户数量
procs=$(ps aux | wc -l) #进程数量
users=$(sed -n '$=' /etc/passwd) #系统总账户数量
cpu_info=$(LANG=C lscpu | awk -F: '/Model name/ {print $2}') #CPU 型号
cpu_core=$(awk '/processor/{core++} END{print core}' /proc/cpuinfo) #CPU 内核数量
yum -y -q install sysstat &>/dev/null #安装性能监控软件
echo -e "\033[34m 提取磁盘性能指标,请稍后...\033[0m"
tps=$(LANG=C sar -d -p 1 6 | awk '/Average/' | \
tail -n +2 | awk '{print "["$2"]磁盘平均 IO 数量:"$3}') &
read_write=$(LANG=C sar -d -p 1 6 | awk '/Average/' | \
tail -n +2 | awk '{print "["$2"]平均每秒读写扇区量:"$4,$5}') &
irq=$(vmstat 1 2 | tail -n +4 | awk '{print $11}') #中断数量
cs=$(vmstat 1 2 | tail -n +4 | awk '{print $12}') #上下文切换数量
top_proc_mem=$(ps --no-headers -eo comm,rss | sort -k2 -n | tail -10) #占用内存资源最多的 10 个进程列表
top_proc_cpu=$(ps --no-headers -eo comm,pcpu | sort -k2 -n | tail -5) #占用 CPU 资源最多的 5 个进程列表
#获取网卡流量,接收|发送的数据流量,单位为字节(bytes)
net_monitor=$(cat /proc/net/dev | tail -n +3 | \
awk 'BEGIN{ print "网卡名称 入站数据流量(bytes) 出站数据流量(bytes)" } \
{ print $1,$2,$10 }' | column -t) 
#输出数据信息
echo -e "\033[32m--------------本机主要数据参数表-----------------\033[0m"
echo -e "本机 IP 地址列表:\033[32m$localip\033[0m"
echo -e "本机主机名称:\033[32m$hostname\033[0m"
echo -e "操作系统版本:\033[32m$release\033[0m,内核版本:\033[32m$kernel\033[0m"
echo -e "CPU 型号为:\033[32m$cpu_info\033[0m,\
CPU 内核数量:\033[32m$cpu_core\033[0m"
echo -e "本机总内存容量:\033[32m$mem_total\033[0m,\
剩余可用内存容量:\033[32m$mem_free\033[0m"
echo -e "本机 swap 总容量:\033[32m$swap_total\033[0m,\
剩余容量:\033[32m$swap_free\033[0m"
echo -e "CPU 最近 1 分钟,5 分钟,15 分钟的平均负载分别为:\
\033[32m$load1 $load5 $load15\033[0m"
echo -e "本机总账户数量为:\033[32m$users\033[0m,\
当前登录系统的账户数量:\033[32m$login_users\033[0m"
echo -e "当前系统中启动的进程数量:\033[32m$procs\033[0m"
echo -e "占用 CPU 资源最多的 10 个进程列表为:"
echo -e "\033[32m$top_proc_cpu\033[0m"
echo -e "占用内存资源最多的 10 个进程列表为:"
echo -e "\033[32m$top_proc_mem\033[0m"
echo -e "CPU 中断数量:\033[32m$irq\033[0m,\
CPU 上下文切换数量:\033[32m$cs\033[0m"
echo -e "每个磁盘分区的总容量与剩余容量信息如下:"
echo -e "$disk"
echo -e "$tps"
echo -e "$read_write"
echo -e "$net_monitor"
echo -e "\033[32m------------------The End------------------------\033[0m"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值