12awk实践

2 阅读24分钟

awk实践

基础实践

基础知识

在日常计算机管理中,总会有很多数据输出到屏幕或者文件,这些输出包含了标准输出、标准错误输出。默认情况下,这些信息全部输出到默认输出设备---屏幕。然而,大量的数据输出中,只有一小部分是我们需要重点关注的,我们需要把我们需要的或者关注的这些信息过滤或者提取以备后续需要时调用。早先的学习中,我们学过使用grep来过滤这些数据,使用cut、tr命令提取某些字段,但是他们都不具备提取并处理数据的能力,都必须先过滤,再提取转存到变量,然后在通过变量提取去处理,比如:

内存使用率的统计步骤

  1. 通过free -m提取出内存总量,赋值给变量 memory_totle
  2. 通过free -m提取出n内存使用量,赋值给变量memory_use
  3. 通过数学运算计算内存使用率

需要执行多步才能得到内存使用率,那么有没有一个命令能够集过滤、提取、运算为一体呢?awk命令就可以

awk全称 Aho Weinberger Kernighan报告生成器,awk的三个字母是来自于三个作者的首字母。它是一个功能非常强大的文档编辑工具,它不仅能以行为单位还能以列为单位处理文件,并且还具有格式化文本输出功能。目前它受自由软件基金会(FSF)进行开发和维护,通常也称它为 GNU AWK,AWK有多种版本:

  • AWK:原先来源于 AT & T 实验室的的AWK
  • NAWK:New awk,AT & T 实验室的AWK的升级版
  • GAWK:即GNU AWK。所有的GNU/Linux发布版都自带GAWK,它与AWK和NAWK完全兼容

原理解读

awk认为文件中的每一行是一条记录,记录与记录的分隔符是换行符,每一列是一个字段,字段与字段的分隔符默认是一个或多个空格或tab制表符。

awk的工作方式是逐行读取文本数据,将每一行数据视为一条记录(record)每条记录以字段分隔符分成若干字段,然后输出各个字段的值。然后以查找匹配某个特定模式的文本行,并对这些文本执行制定动作

基本格式

格式:
    awk [参数] '[动作]' [文件名]
    awk [参数] –f 动作文件 var=value [文件名]
    awk [参数] 'BEGIN段 [动作] END段' [文件名]
注意:
	动作的格式  '匹配条件{打印动作}'
常见参数:
    -F				指定列的分隔符,默认一行数据的列分隔符是空格
    -f file 		指定读取程序的文件名
    -v var=value	自定义变量
awk程序运行优先级是:
    1 BEGIN: 在开始处理数据流之前执行,可选项
    2 动作: 如何处理数据流,必选项
    3 END: 处理完数据流后执行,可选项
常见动作
    print	显示内容
    $0		显示当前行所有内容
    $n		显示当前行的第n列内容,如果存在多个$n,它们之间使用逗号(,)隔开
注意:
	如果打印的内容是变量,则无需在变量两侧加上双引号,其他的都应该加双引号

其他功能

printf 格式化显示内容
	printf [-v var] format [item1,item2,...]
	注意:
		printf输出需要指定换行符号,format的格式必须与后面item对应
		常见格式:
			%c		显示字符的ASCII码		%d|i 	显示十进制整数		%e|E	显示科学计数法数值
			%f		显示浮点数			 %s		 显示字符串			%u	  显示无符号整数
			%%		显示%本身				
		修饰符:
			%#[.#]	第一个#控制显示宽度,第二个#表示小数点后的精度,例如%3.1f
			%-		左对齐,%-15s
			%+		显示数值的正负符号,%+d
常见内置变量
    FILENAME 	当前输入文件的文件名,该变量是只读的
    NR 			指定显示行的行号
    FNR			多文件时候,分别计数
    NF 			表示字段数量
    OFS 		输出格式的列分隔符,缺省是空格
    FS 			输入文件的列分隔符,缺省是连续的空格和Tab
    RS			输入记录分隔符,默认情况下为换行符
    ORS			输出记录分隔符,输出时用指定符号代替换行符
    ARGC|ARGV[n] 获取命令的参数个数|参数内容
# 准备工作
cat > awk.txt << EOF
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
nihao awk7 awk8 awk9
EOF

基础语法

信息查看

字段提取:提取一个文本中的一列数据并打印输出,它提供了相关的内置变量。
    $0 表示整行文本
    $1 表示文本行中的第一个数据字段
    $2 表示文本行中的第二个数据字段
    $N 表示文本行中的第N个数据字段
    $NF 表示文本行中的最后一个数据字段
    NR 代表行的行号,在动作外部表示特定行
注意:
   如果打印多列信息,需要使用逗号隔开,否则是内容合并
# 实践1-打印列信息

# 打印第1列的内容
awk '{print $1}' awk.txt
nihao
nihao
nihao

# 打印第3列内容
awk '{print $3}' awk.txt
awk2
awk5
awk8

# 打印最后一列信息
awk '{print $NF}' awk.txt
awk3
awk6
awk9

# 打印所有内容
awk '{print $0}' awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
nihao awk7 awk8 awk9

# 实践2-打印多列信息

# 打印第1,3列内容
awk '{print $1,$3}' awk.txt
nihao awk2
nihao awk5
nihao awk8

# 打印信息时候,合并信息
awk '{print $1$3}' awk.txt
nihaoawk2
nihaoawk5
nihaoawk8

# 使用\t实现内容的分割,需要用""扩住
awk '{print $1"\t"$3}' awk.txt
nihao   awk2
nihao   awk5
nihao   awk8

# 打印列外普通信息
# 如果没有$n的话,表示 print动作执行的次数与文件行数一致
awk '{print "hello awk"}' awk.txt
hello awk
hello awk
hello awk

# 打印fstab的关键信息
grep "^UUID" /etc/fstab | awk {'print $1,$3'}
UUID=f642f98f-d764-4994-9bb2-a2af0e3b86e8 xfs

# 实践3-行号信息输出

# 打印每列的行号信息
awk '{print NR,$0}' awk.txt
1 nihao awk1 awk2 awk3
2 nihao awk4 awk5 awk6
3 nihao awk7 awk8 awk9

# 按照行号打印对应列的内容
awk '{print NR,$NR}' awk.txt
1 nihao
2 awk4
3 awk8

# 实践4-打印特定行内容
# 指定行号打印信息
awk 'NR==1 {print NR,$1,$3}' awk.txt
1 nihao awk2

awk 'NR==2 {print NR,$1,$3}' awk.txt
2 nihao awk5

定制查看

awk默认的信息查看是以空格作为列分隔符的,而对于非空格作为分隔符的内容,我们需要借助于专门的语法实现信息的分割,这里主要用到以下知识:

常见参数:
    -F			指定列的分隔符,默认一行数据的列分隔符是空格
常见内置变量
    FS 			输入文件的列分隔符,缺省是连续的空格和Tab
    RS			输入记录分隔符,默认情况下为换行符
    注意:
    	一般情况下,在输出信息之前进行格式的调整,需要在BEGIN{}部分设定
# 实践1-简单演示

# 准备文件内容
head -n1 /etc/passwd > passwd.txt

cat passwd.txt
root:x:0:0:root:/root:/bin/bash

# 使用普通awk命令展示
# 结果显示:文件中只有一列,无法被awk默认分离
awk '{print $1}' passwd.txt
root:x:0:0:root:/root:/bin/bash

awk '{print $2}' passwd.txt

awk '{print $0}' passwd.txt
root:x:0:0:root:/root:/bin/bash

# 实践2-定制分隔符

# 设定分隔符实现信息的分隔效果
awk -F ':' '{print $0}' passwd.txt
root:x:0:0:root:/root:/bin/bash

awk -F ':' '{print $1}' passwd.txt
root

awk -F ':' '{print $1,$7}' passwd.txt
root /bin/bash

# 基于-v方式设定输入分隔符的环境变量FS
awk -v FS=":" '{print $1FS$7}' passwd.txt
root:/bin/bash

# 虽然-v FS 和 -F 都可以设定入口数据的列分隔符,如果混用的话,后面的会覆盖前面的
awk -v FS="/" '{print $1FS$3}' passwd.txt
root:x:0:0:root:/bin

awk -v FS="/" -F ':' '{print $1FS$3}' passwd.txt
root:0

awk -F ':' -v FS="/" '{print $1FS$3}' passwd.txt
root:x:0:0:root:/bin

# 实践3-统计案例

# 准备网址域名信息
cat > domain.txt << EOF
http://www.example.org/index.html
http://www.example.org/1.html
http://api.example.org/index.html
http://upload.example.org/index.html
http://img.example.org/3.html
http://search.example.org/2.html
EOF

# 对相关信息进行统计
awk -F[/]+ '{print $2}' domain.txt  | uniq -c
      2 www.example.org
      1 api.example.org
      1 upload.example.org
      1 img.example.org
      1 search.example.org

awk -F[/]+ '{print $(NF-1)}' domain.txt  | uniq -c
      2 www.example.org
      1 api.example.org
      1 upload.example.org
      1 img.example.org
      1 search.example.org

显示语法

基础知识

awk支持格式化输出相关信息。它主要依赖两种方法:

属性方法
    OFS 		输出格式的列分隔符,缺省是空格
    ORS			输出记录分隔符,输出时用指定符号代替换行符
print方法
	printf [-v var] format [item1,item2,...]
	注意:
		printf输出需要指定换行符号,format的格式必须与后面item对应
		常见格式:
			%c		显示字符的ASCII码		%d|i 	显示十进制整数		
			%e|E	显示科学计数法数值	  %u	  显示无符号整数
			%f		显示浮点数			 %s		 显示字符串			
			%%		显示%本身				
		修饰符:
			%#[.#]	第一个#控制显示宽度,第二个#表示小数点后的精度,例如%3.1f
			%-		左对齐,%-15s
			%+		显示数值的正负符号,%+d

简单实践

# 实践1-列输出分隔符实践

# 借助于-v方式为命令行输出相关信息
awk -F ':' -v OFS="~~~" '{print $1,$7}' passwd.txt
root~~~/bin/bash

# 借助于BEGIN语句设定环境变量
awk -F ':' 'BEGIN{OFS="~"} {print NR,$1,$7}' passwd.txt
1~root~/bin/bash

# 在BEGIN内部同时实现多个环境变量
# awk 'BEGIN{FS=":";OFS="-"} NR==1 {print $1,$3,$NF}' passwd.txt
awk 'BEGIN{FS=":";OFS="-"}NR==1{print $1,$3,$NF}' passwd.txt
root-0-/bin/bash

# 实践2-行输入输出分隔符实践

# 定制输入分隔符
seq 7 | awk 'BEGIN{RS=""} {print $1,$2,$3,$4,$5,$6,$7}'
1 2 3 4 5 6 7

# 借助于-v方式为行分隔符输出相关信息
# 列分隔符为":",每一行只有1列,$0表示所有列也就是第1列;这样第一行:第1列 第1列;第二行:第2列(空) 第1列;第二行:第3列(空) 第1列。输出时行与行之间的分隔符由换行符替换成"|",输出时列与列之间的分隔符依然是空格(第2、3行,内容则是空格加上该行的第1列)
awk -F ':' -v ORS="|" '{print $NR,$0}' awk.txt
nihao awk1 awk2 awk3 nihao awk1 awk2 awk3| nihao awk4 awk5 awk6| nihao awk7 awk8 awk9|

# 借助于BEGIN语句设定环境变量
awk -F":" 'BEGIN{ORS="|"} {print NR,$0}' awk.txt
1 nihao awk1 awk2 awk3|2 nihao awk4 awk5 awk6|3 nihao awk7 awk8 awk9|

# 实践3-printf格式化输出实践

# 使用print的格式信息
awk '{printf "%s\n",$1}' awk.txt
nihao
nihao
nihao

awk '{printf "%s",$1}' awk.txt; echo
nihaonihaonihao

# 多信息格式化嵌套
awk '{printf "%d--%s--%s\n",NR,$1,$NR}' awk.txt
1--nihao--nihao
2--nihao--awk4
3--nihao--awk8

# 数字的格式化输出
awk '{printf "%4.2f--%s\n", NR,$1}' awk.txt
1.00--nihao
2.00--nihao
3.00--nihao

# 字符的格式化输出
awk '{printf "%-8s%s\n",NR,$1}' awk.txt
1       nihao
2       nihao
3       nihao

优先级实践

基础知识

awk为了体现程序的逻辑顺序,划分了三个代码段,其结构如下:
格式显示:
	BEGIN{}: 读入第一行文本之前执行的语句,一般用来初始化操作
    {}: 逐行处理的执行命令
    END{}: 处理完最后以行文本后执行,一般用来处理输出结果

简单实践

# 实践1-分结构实践

# BEGIN设定数据处理的前置准备
awk 'BEGIN{OFS=":"} {print NR,$0}' awk.txt
1:nihao awk1 awk2 awk3
2:nihao awk4 awk5 awk6
3:nihao awk7 awk8 awk9

# {}定制输出的内容样式
awk '{ print "第一列:"$1,"第二列:"$2 }' awk.txt
第一列:nihao 第二列:awk1
第一列:nihao 第二列:awk4
第一列:nihao 第二列:awk7

# END设定信息处理完毕后的收尾动作
awk 'END{printf "----------------\n行数总计: %2d\n", NF}' awk.txt
----------------
行数总计:  4

# 实践2-优先级演示
# 结果可知:begin的优先级 > 命令优先级 > END的优先级
awk -F: 'BEGIN{print "begin中的NR值:" NR}  NR==11 {print "命令中的NR值: " NR}END{print "END中的NR值: " NR}' /etc/passwd
begin中的NR值:0
命令中的NR值: 11
END中的NR值: 24

# 实践3-组合演练

# BEGIN 和 {} 实现信息的头部格式化
awk 'BEGIN{print "第一列\t第二列\n----------------"}{print $1"\t"$2}' awk.txt
第一列  第二列
----------------
nihao   awk1
nihao   awk4
nihao   awk7

# 完全组合实现信息的头部和尾部格式化操作
awk 'BEGIN{print "第一列\t第二列\n----------------";total=0;}{print $1"\t"$2;total = NR}END{printf "----------------\n行数总计: %2d\n", total}' awk.txt
第一列  第二列
----------------
nihao   awk1
nihao   awk4
nihao   awk7
----------------
行数总计:  3

# 统计文件格式化
awk -F":" 'BEGIN{printf "---------------------------\n%-12s|%9s|\n---------------------------\n","用户名","shell类型"}{printf "%-15s|%1                             0s|\n---------------------------\n",$1,$7}END{printf "用户总数总: %2d\n", NR}' passwd.txt
---------------------------
用户名         |  shell类型|
---------------------------
root           | /bin/bash|
---------------------------
用户总数总:  1

# 定制配置考试成果表
cat > course_scores.txt << EOF
张三 	100	 56	   99
李四 	90	 68	   89
王五 	50	 78	   67
赵六 	80	 99    89
EOF

# 格式化显示
awk 'BEGIN{sep="----------------------\n";printf "%s|%-3s|%2s|%2s|%2s|\n%s",sep,"姓名","语文","数学","历史",sep;} NR>=2 {printf "|%-3s|%4d|%4d|%4d|\n",$1,$2,$3,$4} END{printf "%s学生 总数: %2d\n",sep,NR}' course_scores.txt
----------------------
|姓名 |语文|数学|历史|
----------------------
|李四 |  90|  68|  89|
|王五 |  50|  78|  67|
|赵六 |  80|  99|  89|
----------------------
学生 总数:  4

变量实践

基础知识

在shell中,除了通用的字符串操作之外,还具有一些丰富灵活性的小功能,比如变量操作、数组操作类的。awk所支持的变量主要有两种类型:内置变量和自定义变量

内置变量
	所谓的内置变量主要就是awk内部已经定制好的变量,我们可以直接拿过来使用,这些常见的变量我们基本上都演示过了,比如:
	FILENAME 	当前输入文件的文件名,该变量是只读的
	FIELDWIDTHS	以空格分隔的数字列表,用空格定义每个数据字段的精确宽度
    NR 			指定显示行的行号
    FNR			多文件时候,分别计数
    NF 			表示字段数量
    OFS 		输出格式的列分隔符,缺省是空格
    FS 			输入文件的列分隔符,缺省是连续的空格和Tab
    RS			输入记录分隔符,默认情况下为换行符
    ORS			输出记录分隔符,输出时用指定符号代替换行符
    ARGC|ARGV[n] 获取命令的参数个数|参数内容
自定义变量
	所谓的自定义变量,主要是根据实际情况,自己定义一些所谓的变量,然后在awk逻辑操作的过程中作为辅助性的措施。自定义变量的定制方法:
	-v var=value
	它可以在 命令行、BEGIN、{}、END 等位置进行使用

简单实践

# 实践1-内置变量的进阶使用

# NF 查看当前所在目录,"/root""/"分隔之后产生两个字段,NF表示字段数量,$NF表示取最后一个字段
echo $PWD | awk -F / '{print $NF}'
root

cd /etc/sysconfig/

echo $PWD | awk -F / '{print $NF}'
sysconfig

cd ~

# 查看文件名称
echo /etc/sysconfig/network-scripts/ifcfg-ens33 | awk -F / '{print $NF}'
ifcfg-ens33

# 查看文件所在路径
echo /etc/sysconfig/network-scripts/ifcfg-ens33 | awk -F / '{print $(NF-1)}'
network-scripts

# 实践2-NR行号的作用

# 快速获取ip地址
ifconfig ens33 | awk 'NR==2{print $2}'
192.168.91.101

ifconfig ens33 | awk '/netmask/{print $2}'
192.168.91.101

# 快速获取文件行数
awk -F: 'END{print NR}' /etc/passwd
24

awk -F: 'END{print NR}' /etc/sysconfig/network-scripts/ifcfg-ens33
19

# 实践3-FNR 和 FILENAME 获取文件基本信息

# 获取文件基本信息
awk '{print FNR,FILENAME,$0}' /etc/issue /etc/redhat-release
1 /etc/issue \S
2 /etc/issue Kernel \r on an \m
3 /etc/issue
1 /etc/redhat-release CentOS Linux release 7.9.2009 (Core)

# FIELDWIDTHS:重定义列宽并打印(不使用分隔符了),注意不要使用$0,因为$0是打印本行全内容,不会打印你定义的字段
awk 'BEGIN{FIELDWIDTHS="5 2 8"}NR==1{print $1,$2,$3}' passwd.txt
root: x: 0:0:root

# 实践4-命令参数

# ARGC 获取命令行参数的个数,包括awk命令
awk 'NR==1 {print ARGC }' awk.txt
2

# ARGV 将命令行所有参数放到一个数组中,ARGV[下标] 获取参数
awk 'NR==1 {print ARGV[0] }' awk.txt
awk

awk 'NR==1 {print ARGV[1] }' awk.txt
awk.txt

# 实践5-自定义变量

# 变量实践
awk -v name='test' 'BEGIN{print name}'
test

awk 'BEGIN{name="test";print name}'
test

# 定制格式化输出
awk -F: '{age=36; address="beijing";print $1,age,address}' passwd.txt
root 36 beijing

进阶知识

赋值运算

基础知识

awk本质上属于一种编程语言,所以它具有编程语言的一般功能,表达式、流程控制等基本上都在awk中具有想当程度的使用

	awk的表达式包括很多种类,常见的表达式有:
	算术操作符:+ - * / ^ %
	赋值操作符:= += -= /= ++ -- %= ^=
	比较操作符:== != > >= < <= 
	模式匹配符:~ 左边是否与右边匹配包含,!~ 是否不匹配
	逻辑操作符:与&&、或||、非!

简单实践

# 实践1-字符串赋值
awk -v name='test' 'BEGIN{print name}'
test

awk 'BEGIN{city="beijing";print city}'
beijing

# 实践2-数据赋值
# 命令区域段内进行数据赋值操作
echo | awk '{i=10;print i+=1}'
11

echo | awk '{i=10;print i++,i}'
10 11

echo | awk '{i=10;print ++i,i}'
11 11

echo | awk '{i=10;print --i,i}'
9 9

echo | awk '{i=10;print i--,i}'
10 9

# 在BEGIN段是可以的,由于END段主要是收尾的信息显示,所以基本不做计算层次的功能
awk 'BEGIN{i=0;print i++,i}'
0 1

awk 'BEGIN{i=0;print ++i,i}'
1 1

# 实践3-变量赋值
# -v 设定变量进行赋值操作
# 处理第1行时,n++返回0,表示false,则第1行不显示;处理其它行时,n++返回的都是大于0的值,都表示true,则其它行都显示
awk -v n=0 'n++' awk.txt
nihao awk4 awk5 awk6
nihao awk7 awk8 awk9

# !对n++的返回值取反,效果与上面的相反
awk -v n=0 '!n++' awk.txt
nihao awk1 awk2 awk3

# 实践4-数组赋值
# 在awk中可以设定数组
awk 'BEGIN{array[0]=100;print array[0]}'
100

数学运算

基础知识

	所谓的数学运算,其实就是我们平常所说的二元运算,常见的运算符号有:
		+ - * / ^ %

简单实践

# 实践1-普通数学运算
awk 'BEGIN{print 100+3 }'
103

awk 'BEGIN{print 100-3 }'
97

awk 'BEGIN{print 100*3 }'
300

awk 'BEGIN{print 100/3 }'
33.3333

awk 'BEGIN{print 100**3 }'
1000000

awk 'BEGIN{print 100%3 }'
1

# 实践2-BEGIN和变量运算
awk -v 'count=0' 'BEGIN{count++;print count}'
1

awk -v 'count=0' 'BEGIN{count--;print count}'
-1

# 实践3-案例解读
# 在之前的案例基础上,进行统计运算每个学生的总分,每个班级的课程总分
awk 'BEGIN{sep="---------------------------\n";col_format="|%-3s|%4d|%4d|%4d|%4d|";printf "%s|%-3s|%2s|%2s|%2s|%2s|\n%s",sep,"姓名","语文","数学","历史","总分",sep;yu=0;shu=0;li=0;total;} NR>=1 {yu=$2+yu;shu+=$3;li+=$4;total=$2+$3+$4; printf col_format"\n",$1,$2,$3,$4,total} END{printf "%s"col_format"\n学生 总数: %2d\n",sep,"合计",yu,shu,li,yu+shu+li,NR}' course_scores.txt
---------------------------
|姓名 |语文|数学|历史|总分|
---------------------------
|张三 | 100|  56|  99| 255|
|李四 |  90|  68|  89| 247|
|王五 |  50|  78|  67| 195|
|赵六 |  80|  99|  89| 268|
---------------------------
|合计 | 320| 301| 344| 965|
学生 总数:  4

逻辑运算

基础知识

	所谓的逻辑运算,其实指的就是 与或非的操作。基本语法格式如下:
        与&& - 并且关系
        或|| - 或者关系
        非!  - 取反关系

简单实践

# 实践1-基本逻辑运算

# 与运算:真真为真,真假为假,假假为假
awk 'BEGIN{print 100>=2 && 100>=3 }'
1

awk 'BEGIN{print 100>=2 && 1>=100 }'
0

# 或运算:真真为真,真假为真,假假为假
awk 'BEGIN{print 100>=2 || 1>=100 }'
1

awk 'BEGIN{print 100>=200 || 1>=100 }'
0

# 实践2-逻辑运算
awk -F: '$3==0 || $3>=1000 {print $1}' /etc/passwd
root
zhang
nfsnobody

awk -F: '$3==0 || $3>=1000 {print $1,$3}' /etc/passwd
root 0
zhang 1000
nfsnobody 65534

awk -F: '$3==0 && $3>=1000 {print $1,$3}' /etc/passwd

awk -F: '!($3<1000) {print $1,$3}' /etc/passwd
zhang 1000
nfsnobody 65534

awk -F: 'NR>=1&&NR<=2{print NR,$0}' /etc/passwd
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin

# 实践3-非关系
# 当i为空、空字符串、0,!i 表示true(1),!!i 表示false(0)
awk 'BEGIN{print i}'

awk 'BEGIN{print !i}'
1

awk -v i=10 'BEGIN{print !i}'
0

awk -v i=-3 'BEGIN{print !i}'
0

# awk -v i="0" 'BEGIN{print !i}'
awk -v i=0 'BEGIN{print !i}'
1

awk -v i=abc 'BEGIN{print !i}'
0

awk -v i='' 'BEGIN{print !i}'
1

匹配运算

基础知识

所谓的匹配运算,主要指的是关键字无法精确性地匹配相关信息了,但是我们可以结合一些关键字信息进行模糊地匹配。对于匹配运算来说,它有一些需要注意的事情,具体内容如下:

注意事项:
	如果没有指定,为空模式,匹配每一行
	如果指定"/匹配条件/",则表示仅处理能够匹配到的内容
	如果指定关系表达式,只有结果为真的情况下,才会被处理
		真:结果为非0值、非空字符串,空格也是真
		假:结果为空字符串或0值,数值不用加""

简单实践

# 实践1-真假值匹配

# 假值匹配,下面两行命令都没有输出
awk '"" {print $1,$3}' awk.txt
awk '0 {print $1,$3}' awk.txt

# 非零真值匹配,下面三行命令都有相同的输出
awk '"aaa" {print $1,$3}' awk.txt
awk '9 {print $1,$3}' awk.txt
awk -v n=8 'n{print $1,$3}' awk.txt
nihao awk2
nihao awk5
nihao awk8

# 实践2-内容匹配

# 内容匹配
awk -F ':' '$1 ~ "^ro" {print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash

awk -F ':' '$1 ~ "ftp" {print $0}' /etc/passwd
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin

awk -F ':' '$1 ~ "^[a-d].*" {print $0}' /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
chrony:x:998:996::/var/lib/chrony:/sbin/nologin

# 内容不匹配
awk -F ':' '$1 !~ "^[a-r].*" {print $0}' /etc/passwd
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
zhang:x:1000:1000:zhang:/home/zhang:/bin/bash
tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin

# 实践3-简单案例
# 准备不规则格式文件
cat > zhengli.txt << EOF
  http://www.sswang.org/index.html
      http://www.sswang.org/1.html
    http://api.sswang.org/index.html
asdfasdfsd
  adfsdf
EOF

# 文件的规整
# 这里的匹配条件实际没有用,awk '{print NR"--->"$1}' zhengli.txt
awk '/^[ \t]*/{print NR"--->"$1}' zhengli.txt
1--->http://www.sswang.org/index.html
2--->http://www.sswang.org/1.html
3--->http://api.sswang.org/index.html
4--->asdfasdfsd
5--->adfsdf

# 实践4-扩展实践

# 多值匹配打印
# i=1 返回1表示true,则打印每一行;j=1{print $1,$3} 返回1表示true,则打印每一行的第1和第3列。等价于 awk 'i=1{print $0};j=1{print $1,$3}' awk.txt
awk 'i=1;j=1{print $1,$3}' awk.txt
nihao awk1 awk2 awk3
nihao awk2
nihao awk4 awk5 awk6
nihao awk5
nihao awk7 awk8 awk9
nihao awk8

# 假值匹配
awk '0' awk.txt

# 真值匹配
awk '!0' awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
nihao awk7 awk8 awk9

# 非零真值匹配
awk '0-2' awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
nihao awk7 awk8 awk9

# 零值匹配
awk '0-0' awk.txt
awk '9-9' awk.txt

# 非零真值匹配,处理每行时++n返回的都是行号,都表示真
awk -v n=0 '++n' awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6
nihao awk7 awk8 awk9

内置函数1

基础知识

在awk内部预制了一些函数,借助于这些函数,我们可以实现相关场景的快速操作。这些内置函数的常见类型有:

数值类内置函数
	int(expr)     截断为整数:int(123.45)和int("123abc")都返回123,int("a123")返回0;以0开头,则将其识别为8进制并转换为十进制;以0x或0X开头,则将其识别为16进制并转换为十进制;该函数会尽最大努力转换字符串中的每个字符,直到不能转换为止
    sqrt(expr)    返回平方根
    rand()        返回[0,1)之间的随机数,默认使用srand(1)作为种子值
    srand([expr]) 设置rand()种子值,省略参数时将取当前时间的epoch值(精确到秒的epoch)作为种子值
字符串类内置函数
	sprintf(format, expression1, ...):返回格式化后的字符串
	length():返回字符串字符数量、数组元素数量、或数值转换为字符串后的字符数量
	strtonum(str):将字符串转换为十进制数值
        如果str以0开头,则将其识别为8进制
        如果str以0x或0X开头,则将其识别为16进制
        会尽最大努力转换字符串中的每个字符,直到不能转换为止
	tolower(str):转换为小写
	toupper(str):转换为大写
	index(str,substr):从str中搜索substr(子串),返回搜索到的索引位置(位置从1开始),搜索不到则返回0
数据操作内置函数
	substr(string,start[,length]):从string中截取子串;start从1开始,包括start;length可选,没有的话表示截取后面所有的
	split(string, array [, fieldsep [, seps ] ]):将字符串分割后保存到数组array中;fieldsep可选,是分隔符(默认是连续的空白);seps可选,是保存分隔符的数组(fieldsep必须有,才可以有seps)。array数组和seps数组,操作时下标从1开始
	match(string,reg[,arr]):使用reg正则规则匹配string信息,默认返回匹配的索引(从1开始),可以将内容存到数组(该数组下标从0开始)

简单实践

# 实践1-数据值函数实践

# int数据取整
awk 'BEGIN{print int(123.45)}'
123
awk 'BEGIN{print int(123.565)}'
123
# 八进制
awk 'BEGIN{print int(011)}'
9
# 尽最大努力转换
awk 'BEGIN{print int(011aa)}'
9
# 十六进制
awk 'BEGIN{print int(0x10)}'
16
# 尽最大努力转换
awk 'BEGIN{print int(0x10qq)}'
16
awk 'BEGIN{print int(9aa)}'
9
awk 'BEGIN{print int(aa)}'
0
# sqrt数据求平方根
awk 'BEGIN{print sqrt(9)}'
3
# rand()求0-1的随机数,默认使用srand(1)作为种子值,多次执行结果都一样
awk 'BEGIN{print rand()}'
0.237788

# srand() 设定rand()的随机种子值;如果种子值固定,rand()值也固定
awk 'BEGIN{srand();print rand()}'
0.811752
awk 'BEGIN{srand();print rand()}'
0.153729

awk 'BEGIN{srand(2);print rand()}'
0.610198
awk 'BEGIN{srand(2);print rand()}'
0.610198

# 结合int()获取随机整数
# 获取0-9之间的随机值
awk 'BEGIN{srand();print int(10*rand())}'
6

# 实践2-字符串实践

# sprintf设定数据基本样式
awk 'BEGIN{a=sprintf("%s-%d-%s","abc",23,"ert"); print a}'
abc-23-ert

# length获取字符串长度
awk 'BEGIN{v="nsfadsafdsaf";print length(v)}'
12
# length获取数组长度
awk 'BEGIN{v[0]=1;v[8]=3;v[10]=2;print length(v)}'
3

# strtonum 将字符串转换为十进制数值
awk 'BEGIN{print strtonum(123.45)}'
123.45
# 八进制
awk 'BEGIN{print strtonum(011)}'
9
# 尽最大努力转换
awk 'BEGIN{print strtonum(011aa)}'
9
# 十六进制
awk 'BEGIN{print strtonum(0x10)}'
16
# 尽最大努力转换
awk 'BEGIN{print strtonum(0x10qq)}'
16
awk 'BEGIN{print strtonum(9aa)}'
9
awk 'BEGIN{print strtonum(aa)}'
0

# tolower转换为小写
awk 'BEGIN{v="ADMIN";print tolower(v)}'
admin

# toupper转换为大写
awk 'BEGIN{v="nsfad";print toupper(v)}'
NSFAD

# index查找子字符串的位置
awk 'BEGIN{str="nsfad";print index(str, "fa")}'
3
awk 'BEGIN{str="nsfad";print index(str, "ns")}'
1
awk 'BEGIN{str="nsfad";print index(str, "ad")}'
4

# 实践3-其他操作

# substr字符串截取
awk 'BEGIN{v="abcdefgh";print substr(v,3)}'
cdefgh
awk 'BEGIN{v="abcdefgh";print substr(v,3,3)}'
cde

# split切割字符串
# arr和seps数组,下标从1开始
awk 'BEGIN{split("abc-def-gho-pq",arr,"-",seps); print length(arr),length(seps),arr[1],arr[2],arr[3],arr[4],seps[1],seps[2],seps[3]}'
4 3 abc def gho pq - - -
awk 'BEGIN{split("abcde",arr,"-");print arr[1]}'
abcde
echo "12:34:56" | awk '{split($0,a,":");print a[1],a[2],a[3]}'
12 34 56

# match字符串匹配
# 返回搜索到的索引位置(位置从1开始)
awk 'BEGIN{str="safdsajfkdsajlfjdsl";print match(str,"j.*s")}'
7
# 可以将内容存到数组(该数组下标从0开始)
awk 'BEGIN{str="safdsajfkdsajlfjdsl";match(str,"j.*s",arry);print arry[0]}'
jfkdsajlfjds

内置函数2

基础知识

awk还内置了一些关于时间和数据类型的内置函数,具体信息如下

时间类内置函数
	mktime("YYYY MM DD HH mm SS [DST]"):构建一个时间,构建失败则返回-1
	systime():返回当前系统时间点,返回的是秒级epoch值
	strftime([format [, timestamp [, utc-flag] ] ]):将时间格式转换为字符串
数据类型相关内置函数:
    isarray(var):测试var是否是数组,返回1(是数组)或0(不是数组)
    typeof(var):返回var的数据类型,有以下可能的值:
        "array":是一个数组
        "regexp":是一个真正表达式类型,强正则字面量才算是正则类型,如@/a.*ef/
        "number":是一个number
        "string":是一个string
        "strnum":是一个strnum,参考strnum类型
        "unassigned":曾引用过,但未赋值,例如"print f;print typeof(f)"
        "untyped":从未引用过,也从未赋值过

简单实践

# 实践1-时间函数实践

# systime返回当前系统时间
awk 'BEGIN{print systime()}'
1703229616

# mktime设置一个时间
awk 'BEGIN{print mktime("2032 4 29 11 32 19")}'
1966822339

# strftime获取格式日期
awk 'BEGIN{print strftime()}'
Fri Dec 22 15:34:20 CST 2023

# awk中可以使用shell中的命令,命令需要用双引号引起来
# 结合xargs设定时间格式
# mktime返回的是时间戳单位为秒;xargs 接收秒时间戳,-i 将该时间保存到{};date 通过-d指定时间,@表示时间戳单位为秒,{}表示通过xargs -i接收到的秒时间戳,+"%F %T"date命令格式化参数
awk 'BEGIN{print mktime("2032 4 29 11 32 19") | "xargs -i date -d@{} +\"%F %T\""}'
2032-04-29 11:32:19
# 上面的命令与下面的命令效果一样
awk 'BEGIN{print mktime("2032 4 29 11 32 19")}' | xargs -i date -d@{} +"%F %T"
2032-04-29 11:32:19

# 设定2032-04-29 11:00:19基础上减1分钟
awk 'BEGIN{print mktime("2032 4 29 11 -1 19") | "xargs -i date -d@{} +\"%F %T\""}'
2032-04-29 10:59:19

# 设定2032-04-29 00:23:19基础上加1小时
awk 'BEGIN{print mktime("2032 4 29 +1 23 19") | "xargs -i date -d@{} +\"%F %T\""}'
2032-04-29 01:23:19

# 设定时间格式
awk 'BEGIN{print strftime("%F %T %z", mktime("2032 4 29 11 32 19"))}'
2032-04-29 11:32:19 +0800

# 实践2-格式判断实践
# isarray判断是否是一个数组
awk 'BEGIN{v="abcdefgh";print isarray(v)}'
0
# awk中不能使用v=("aa" "bb") 这种方式定义数组
awk 'BEGIN{v=("aa" "bb");print isarray(v)}'
0
awk 'BEGIN{v[0]="aa";v[0]="bb";print isarray(v)}'
1

# typeof函数在awk4.2版本之后才有,可以升级awk版本完成下面的测试
yum -y install gcc g++ make
wget http://ftp.gnu.org/gnu/gawk/gawk-4.2.1.tar.gz
tar xf gawk-4.2.1.tar.gz
cd gawk-4.2.1
./configure
make
make check
make install
# gawk安装完成之后,可能需要重新加载一下环境变量或重启一下

# PROCINFO 这是一个关联数组变量,它保存了进程相关的信息
awk '
  BEGIN{
    for(idx in PROCINFO){
      # awk4.2版本之前,if(isarray(idx)){
      if(typeof(PROCINFO[idx]) == "array"){
        continue
      }
      print idx " -> "PROCINFO[idx]
    }
  }'
ppid -> 1669
pgrpid -> 1710
api_major -> 2
api_minor -> 0
group1 -> 0
gid -> 0
egid -> 0
version -> 4.2.1
FS -> FS
uid -> 0
euid -> 0
pid -> 1710
strftime -> %a %b %e %H:%M:%S %Z %Y

逻辑控制

if条件

基础知识

所谓的条件判断,与我们之前学习的shell条件表达式基本效果一致,所以awk里面仍然会有if条件控制语句,只不过表现样式是shell的if语句的单命令格式而已。在shell中,条件控制主要有两类表现样式:

样式1:普通的非语句
	if(条件){执行语句;...}else {执行语句;...}
	if(条件1){执行语句1}else if(条件2){执行语句2}else{执行语句3}
样式2:三元表达式
	条件表达式?真值表达式:假值表达式
	可以类比为:
        if(条件){
            执行语句
        } else {
            执行语句
        }

简单实践

# 实践1-单if表达式
awk -F: '{if($0 ~ "/bin/bash")print$1}' /etc/passwd
root
zhang

awk -F: '{if($NF=="/bin/bash")print$1}' /etc/passwd
root
zhang

awk  -F' ' '{if(NR>9)print $1}' /etc/fstab
UUID=f642f98f-d764-4994-9bb2-a2af0e3b86e8
/dev/mapper/centos-swap

# 统计只有四个字母的用户,下面两行命令逻辑一样
awk -F":" '{if(length($1)==4){i++;print $1}} END{print "count is "i}' /etc/passwd
awk -F":" 'length($1)==4{i++;print $1} END{print "count is "i}' /etc/passwd

root
sync
halt
mail
dbus
sshd
count is 6

# 统计磁盘的使用量,将倒数第二列的数据转换成数值类型,大于8则打印
df -h | awk '{if(strtonum($(NF-1))>8){print $NF":"$(NF-1)}}'
/:9%
/boot:22%

# 实践2-双分支if实践
# 判断内存使用量
free -m | awk '/^Mem/{if($3 / $2 *100 >= 50){print "内存使用量过高"} else{print "内存使用量正常"}}'
内存使用量正常

free -m | awk '/^Mem/{if($3 / $2 *100 >= 10){print "内存使用量过高"} else{print "内存使用量正常"}}'
内存使用量过高

# 实践3-多if表达式
# 标准格式:
awk '
BEGIN{
  score = 999
  if (score >=0 && score < 60) {
    print "不及格"
  } else if (score >= 60 && score < 90) {
    print "优秀"
  } else if (score >= 90 && score <= 100) {
    print "你还是人么"
  } else {
    print "你觉得我信么?"
  }
}
'
你觉得我信么?

# 简单格式:
awk -F: 'BEGIN{i=0;j=0}{if($3<=500){i++}else{j++}}END{print "uid小于500:"i,"uid大于500:"j}' /etc/passwd
uid小于500:20 uid大于500:4

echo "nan" | awk '{if($1=="nan")print "男";else if($1=="nv") print "女";else print "未知"}'
男

echo "nv" | awk '{if($1=="nan")print "男";else if($1=="nv") print "女";else print "未知"}'
女

echo "hello" | awk '{if($1=="nan")print "男";else if($1=="nv") print "女";else print "未知"}'
未知

# 实践4-三元表达式

# 判断用户类型
awk -F":" '{strtonum($3)>=1000?usertype="普通用户":usertype="系统用户";printf "%-6s:%6s\n",$1,usertype}' /etc/passwd | tail -5
zhang :  普通用户
rpc   :  系统用户
rpcuser:  系统用户
nfsnobody:  普通用户
tss   :  系统用户

# 下面的命令行与上面的逻辑是一样的,效果也是一样的
awk -F":" '{usertype=strtonum($3)>=1000?"普通用户":"系统用户";printf "%-6s:%6s\n",$1,usertype}' /etc/passwd | tail -5

# 判断磁盘的容量
df -h | awk '/^\/dev\/sd/{$(NF-1)>10?disk="full":disk="OK";print $1,$(NF-1),disk}'
/dev/sda1 22% full

cat > course_scores.txt << EOF
姓名  语文	数学  历史
张三 	100	 56	   99
李四 	90	 68	   89
王五 	50	 78	   67
赵六 	80	 99    89
EOF

# 学生基本信息统计
# awk -v total=0 'NR>=2 {total=$2+$3+$4;type=total>=240?"优秀":"良好";printf "姓名: %s, 总分: %4d, 状态: %s\n",$1,total,type}' course_scores.txt
awk -v total=0 'NR>=2, $2+$3+$4>=240?type="优秀":type="良好" {total=$2+$3+$4; printf "姓名: %s, 总分: %4d, 状态: %s\n",$1,$2+$3+$4,type}' course_scores.txt
姓名: 张三, 总分:  255, 状态: 优秀
姓名: 李四, 总分:  247, 状态: 优秀
姓名: 王五, 总分:  195, 状态: 良好
姓名: 赵六, 总分:  268, 状态: 优秀

# 结合BEGIN和END对每个学生的总分进行统计判断后格式化输出
awk 'BEGIN{sep="--------------------------------\n";yu=0;shu=0;li=0;total;printf "%s|%-3s|%2s|%2s|%2s|%2s|%2s|\n%s",sep,"姓名","语文","数学","历史","总分","状态",sep;} NR>=2 {total=$2+$3+$4;type=total>=240?"优秀":"良好";yu+=$2;shu+=$3;li+=$4;printf "|%-3s|%4d|%4d|%4d|%4d|%-2s|\n",$1,$2,$3,$4,total,type} END{printf "%s|%-3s|%4d|%4d|%4d|%4d|\n学生总数: %2d\n",sep,"合计",yu,shu,li,yu+shu+li,NR-1}' course_scores.txt
--------------------------------
|姓名 |语文|数学|历史|总分|状态|
--------------------------------
|张三 | 100|  56|  99| 255|优秀|
|李四 |  90|  68|  89| 247|优秀|
|王五 |  50|  78|  67| 195|良好|
|赵六 |  80|  99|  89| 268|优秀|
--------------------------------
|合计 | 320| 301| 344| 965|
学生总数:  4

switch条件

基础知识

在awk中有一种简单的条件控制语法switch,相较于if来说,switch分支语句功能较弱,只能进行等值比较或正则匹配,一般结合case方式来使用

语法格式
    switch (表达式) {
        case 值1|regex1 : 执行语句1;break
        case 值2|regex2 : 执行语句2;break
        case 值3|regex3 : 执行语句3;break
        ...
        [ default: 执行语句 ]
    }
注意:
	表达式的结果符合awk的逻辑运算,表达式成功返回1,表达式失败返回0
	因为switch本身外侧没有流程控制,所以,一般情况下,当外侧没有循环控制的时候,会结合break来使用
	一旦遇到break,代表退出当前循环

简单实践

# 实践1-简单实践
# break效果,没有break的时候,每一次都会走一个switch的完整循环
seq 2 | awk '{switch($1%2){
  case "1":
    print "奇数:",$1
  case "0":
    print "偶数:",$1
  default:
    print "嘿嘿"
}}'
奇数: 1
偶数: 1
嘿嘿
偶数: 2
嘿嘿

# 结合break的基本语法实践
seq 5 | awk '{switch($1%2){
  case "1":
    print "奇数:",$1
    break
  case "0":
    print "偶数:",$1
    break
  default:
    print "嘿嘿"
    break
}}'
奇数: 1
偶数: 2
奇数: 3
偶数: 4
奇数: 5

# 实践2-结合文本来进行实践
awk '{switch(NR%2){
  case "1":
    print "奇数行:",$0
    break
  case "0":
    print "偶数行:",$0
    break
}}' awk.txt
奇数行: nihao awk1 awk2 awk3
偶数行: nihao awk4 awk5 awk6
奇数行: nihao awk7 awk8 awk9

# 之前awk脚本放在一行显示,多行时,用分号的地方可以用换行替代。`NR>=2 {`这里的空格不可以用换行替代,这是一个整体可以没有空格
awk -v total=0 '
BEGIN {
  printf "\t学生成绩信息统计\n"
  printf "-----------------------------------\n"
}
NR>=2 {
total=$2+$3+$4
switch (total >= 240) {
  case 0:
    type="良好";break
  case 1:
    type="优秀";break
}
printf "姓名: %-3s 总分: %4d,状态: %-2s\n",$1,total,type
}' course_scores.txt
        学生成绩信息统计
-----------------------------------
姓名: 张三  总分:  255,状态: 优秀
姓名: 李四  总分:  247,状态: 优秀
姓名: 王五  总分:  195,状态: 良好
姓名: 赵六  总分:  268,状态: 优秀

for循环

基础知识

在awk中,支持一些逻辑循环的功能,比如 for、while等。实际的过程中,我们往往会结合数组元素进行信息的统计

for语句标准格式
	for (三元表达式) {
        执行语句
    }

    for (变量 in 列表) {
        执行语句
    }
    
for语句简写格式
	for(表达式) {执行语句;...}
	
注意:
	for语句可以结合if语句进行操作

简单实践

# 实践1-for循环语法

# 普通for循环实践
for((i=1,sum=0;i<=100;i++));do let sum+=i;done;echo $sum
5050

# awk的for标准语法
awk 'BEGIN {
  sum=0
  for (i=1;i<=100;i++) {
    sum+=i
  }
  print sum
}'
5050

# awk for循环单行实践
awk 'BEGIN{sum=0;for(i=1;i<=100;i++){sum+=i} print sum}'
5050

# 实践2-文本实践

# 文本信息的基本统计
seq 10 | paste -s | tr -s "\t" " " > num.txt

cat num.txt
1 2 3 4 5 6 7 8 9 10

awk '{for(i=1;i<=NF;i++){sum+=$i}print sum}' num.txt
55

# 学生信息统计
awk 'NR>=2 {
  total=0
  for (i=2;i<=NF;i++) {
    total += $i
  }
  print "学生姓名: "$1", 课程总分: "total
}' course_scores.txt
学生姓名: 张三, 课程总分: 255
学生姓名: 李四, 课程总分: 247
学生姓名: 王五, 课程总分: 195
学生姓名: 赵六, 课程总分: 268

while循环

基础知识

awk支持while相关的循环处理,它主要有两种表现样式

样式1
    while(条件){
        执行语句
    }

样式2
    do {
        执行语句
    } while(条件)
    
简写样式
	while(condition){执行语句;...}
	do {条件;...} while(条件)

简单实践

# 实践1-while实践
# while标准格式
awk 'BEGIN {
  i=1;sum=0
  while(i<=100) {
    sum+=i
    i++
  }
  print sum
}'
5050

awk 'BEGIN{i=1;sum=0;while(i<=100){sum+=i;i++};print "sum="sum}'
sum=5050

# 指定数据进行求和
read -p "请输入一个数字:" NUM;awk -v num=$NUM 'BEGIN{i=1;sum=0;while(i<=num){sum+=i++};print "1~"num"的和为="sum}'
请输入一个数字:100
1~100的和为=5050

# 最大值和最小值
echo '0 234 252 3246 2245 2345 4536 3754 32 345 323 234 3 1' > num.txt

# awk '{min=$1;max=$1;i=2;while(i<=NF){if(max<$i){max=$i}if(min>$i){min=$i}i++}print "max:"max,"min:"min}' num.txt
awk '{min=$1;max=$1;i=2;while(i<=NF){if(max<$i)max=$i;if(min>$i)min=$i;i++}print "max:"max,"min:"min}' num.txt
max:4536 min:0

# 标准求和
awk '{
  sum=0
  i=1
  while (i<=NF) {
    sum+=$i
    i++
  }
  print sum
}' num.txt
17550

# 实践2-dowhile实践
# 基本格式
awk 'BEGIN{i=0;do {print i;i++} while(i<5)}'
0
1
2
3
4

awk 'BEGIN{i=1;sum=0;do{sum+=i;i++}while(i<=100)print sum}'
5050

流程控制

基础知识

	awk提供了很多的功能实践,尤其是与逻辑控制相关的,其实awk为了更好的进行这些流程的控制,它也提供了很多的控制语法,这些语法如下:
	continue 	中断本次循环
	break 		中断整个循环
	next 		可以提前结束对匹配行处理而直接进入下一行处理
	nextfile 	进阶版的next,可以提前结束对匹配行处理,直接读取下一个文件进行循环处理
	exit 		退出awk程序
		END代码段属于exit一部分,可以在BEGIN或main段中执行exit操作--执行END语句块。

简单实践

# 实践1-continue实践

# continue输出满足条件的数据
awk '
BEGIN{
  for(i=0;i<5;i++){
    if(i==2)continue
    print(i)
  }
}'
0
1
3
4

# continue 求奇|偶数和
awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i%2==0)continue;sum+=i}print sum}'
2500

awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i%2!=0)continue;sum+=i}print sum}'
2550

# 实践2-break实践

awk '
BEGIN{
  for(i=0;i<5;i++){
    if(i==2)break
    print(i)
  }
}'
0
1

# 循环求和,当加值为66的时候,停止运算
awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i==66)break;sum+=i}print sum}'
2145

# 实践3-next实践

cat awk.txt -n
     1  nihao awk1 awk2 awk3
     2  nihao awk4 awk5 awk6
     3  nihao awk7 awk8 awk9

# 输出指定行外的其他行
awk 'NR==3{next}{print}' awk.txt
nihao awk1 awk2 awk3
nihao awk4 awk5 awk6

awk 'NR==2{next}{print}' awk.txt
nihao awk1 awk2 awk3
nihao awk7 awk8 awk9

# 实践4-nextfile实践
awk 'FNR==2{nextfile}{print}' awk.txt awk.txt
nihao awk1 awk2 awk3
nihao awk1 awk2 awk3

# 实践5-exit实践

# 处理一个文件就退出awk程序
awk '{print $2}' awk.txt
awk1
awk4
awk7

awk '{print $2; exit}' awk.txt
awk1


# 只处理三行匹配的内容后续结束
awk '/nologin/{i++;if(i<=3){print $0}else{exit}}' /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin

# 多个阶段实践exit
awk 'BEGIN{flag=1;exit 2}{}END{if(flag){exit 1}}';echo $?
1

awk 'BEGIN{exit 2}{}END{if(flag){exit 1}}';echo $?
2

echo | awk 'BEGIN{}{exit 111}END{if(flag){exit 1}}';echo $?
111

echo | awk 'BEGIN{}{}END{if(flag){exit 1}}';echo $?
0

数组实践

基础知识

在使用awk的时候,其实在一些业务的数据分析场景下,使用频率最多的应该是数组,而且数据往往与前面学习的逻辑流程控制组合在一起使用,数组一般很少单独使用。其基本语法格式如下:

定义数组:
	array[index表达式]
	index表达式:
        可使用任意字符串;字符串要使用双引号括起来
        如果某数组元素事先不存在,在引用时,awk会自动创建此元素,并将其值初始化为空串
        若要判断数组中是否存在某元素,要使用index in array格式进行遍历

遍历数组中的元素,要使用for循环
	for(var in array){for-body}
	注意:
		var会遍历array的每个索引,var不能用关键字index

简单实践

# 实践1-数组简单实践

# 数组的简单定义和调用
awk 'BEGIN{arr["yu"]=78;arr["shu"]=89;arr["li"]=99;print arr["yu"],arr["shu"],arr["li"]}'
78 89 99

# 数组的遍历操作
awk 'BEGIN{arr["yu"]=78;arr["shu"]=89;arr["li"]=99;for(i in arr){print arr[i]}}'
89
78
99

# 内容自动遍历
cat passwd.txt
root:x:0:0:root:/root:/bin/bash

awk -F: '{for(i=1;i<=NF;i++)arr[i]=$i;for(j in arr)print arr[j]}' passwd.txt     root
x
0
0
root
/root
/bin/bash

# 实践2-数组数据去重

# 文件内容准备
echo -e "a\nb\na\ncc" > array.txt

# 数组去重的逻辑
# 语法解读:array[$0]++  以$0等于a为例,第一次执行array[a]++,返回0表示false不打印;第二次执行array[a]++,返回1表示true打印,这样就可以将重复的数据全部打印出来。!array[$0]++,逻辑与上面的相反,可以打印出所有数据,且重复数据只打印一次
awk 'arr[$0]++' array.txt
a

awk '!arr[$0]++' array.txt
a
b
cc

# 实践3-统计计数
echo "a.b.c,c.d" | awk -F'[.,]' '{for(i=1;i<=NF;i++)arr[$i]++}END{for(i in arr)print i,arr[i]}'
a 1
b 1
c 2
d 1

自定义函数

基础知识

虽然awk提供了内置函数来实现相应的功能,但是有些功能场景,还是需要我们自己来设定,这就用到了awk的自定义函数功能了。awk的函数目的,与shell的函数目的一致,都是提高代码的复用能力和功能灵活性

语法格式
    function 函数名(参数1, 参数2, ...)
    {
        函数体代码
    }
注意:
	函数名不能用awk的关键字信息

简单实践

# 实践1-简单函数实践
awk '
function add_func(num1, num2)
{
  return num1 + num2
}
function sub_func(num1, num2)
{
  if (num1 > num2) return num1 - num2
  return num2 - num1
}
BEGIN {
  sum_result=add_func(10, 20)
  print "两值之和为: "sum_result
  sub_result=sub_func(10, 20)
  print "两值之差为: "sub_result
}
'
两值之和为: 30
两值之差为: 10

# 实践2-数据统计计算
# 在之前的案例基础上,进行统计运算每个学生的总分,每个班级的课程总分
awk '
function head_func() {
  printf sep"|%-3s|%2s|%2s|%2s|%2s|\n"sep,"姓名","语文","数学","历史","总分"
}
function body_func(arg1, arg2, arg3, arg4, arg5){
  printf col_format,arg1,arg2,arg3,arg4,arg5
}
function tail_func(arg1, arg2, arg3, arg4, arg5){
  printf sep""col_format""sep"学生总数: %2d\n","合计",arg1,arg2,arg3,arg4,arg5
}
BEGIN {
  sep="---------------------------\n"
  col_format="|%-3s|%4d|%4d|%4d|%4d|\n"
  head_func()
  yu=0;shu=0;li=0;total
}
NR>=2 {
  yu+=$2;shu+=$3;li+=$4;total=$2+$3+$4
  body_func($1,$2,$3,$4,total)
}
END{
  tail_func(yu,shu,li,yu+shu+li,NR-1)
}' course_scores.txt
---------------------------
|姓名 |语文|数学|历史|总分|
---------------------------
|张三 | 100|  56|  99| 255|
|李四 |  90|  68|  89| 247|
|王五 |  50|  78|  67| 195|
|赵六 |  80|  99|  89| 268|
---------------------------
|合计 | 320| 301| 344| 965|
---------------------------
学生总数:  4

综合实践

网络实践

所谓的网络实践,主要是借助于awk的数组功能,进行站点的信息统计操作

# 安装nginx,http://nginx.org/en/linux_packages.html
# nginx不在CentOS官方yum源里面,位于第三方yum源
# 运行安装rpel源命令,安装完成之后你就可以直接使用yum来安装额外的软件包,也就是epel源
yum -y install epel-release
yum -y install nginx
systemctl restart nginx.service
# 重置网站首页
echo 'hello nginx' > /usr/share/nginx/html/index.html

curl localhost
hello nginx

curl localhost/nihao -I -s | head -1
HTTP/1.1 404 Not Found

# 模拟外网访问
curl http://192.168.91.101/ -s -I -H "X-Forwarded-For: 2.2.2.2" | head -1
HTTP/1.1 200 OK

tail -n1 /var/log/nginx/access.log
192.168.91.101 - - [27/Dec/2023:08:41:46 +0800] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.29.0" "2.2.2.2"

# 准备ip地址文件
cat > ip.txt << EOF
112.64.233.130
114.101.40.170
123.15.24.200
125.46.0.62
223.243.252.155
122.228.19.92
218.2.226.42
124.205.143.213
EOF

准备站点访问测试脚本

curl_web_site.sh

#!/bin/bash
# 功能:模拟外网访问网站
while true
do
  cat ip.txt | while read ip
  do
    NUM=$(echo $ip | cut -d"." -f 4)
    for i in $(seq $NUM)
    do
      curl http://192.168.91.101/ -s -I -H "X-Forwarded-For: $ip" >> /dev/null
      curl http://192.168.91.101/$NUM/ -s -I -H "X-Forwarded-For: $ip" >> /dev/null
    done
    sleep 1
  done
done
/bin/bash curl_web_site.sh
# 打开另一个终端进行信息统计

# 实践1-基本信息统计
# 查看当前系统的链接状态数量,脚本启动之前展示的信息较少,脚本启动之后有很多输出信息需要统计分析
ss -ant
State       Recv-Q Send-Q   Local Address:Port                  Peer Address:Port
LISTEN      0      100          127.0.0.1:25                               *:*
LISTEN      0      128                  *:80                               *:*
LISTEN      0      128                  *:22                               *:*
ESTAB       0      0       192.168.91.101:22                    192.168.91.1:5341
ESTAB       0      0       192.168.91.101:22                    192.168.91.1:14080
ESTAB       0      0       192.168.91.101:22                    192.168.91.1:14081
ESTAB       0      48      192.168.91.101:22                    192.168.91.1:14749
ESTAB       0      0       192.168.91.101:22                    192.168.91.1:5339
ESTAB       0      0       192.168.91.101:22                    192.168.91.1:14750
LISTEN      0      100              [::1]:25                            [::]:*
LISTEN      0      128               [::]:80                            [::]:*
LISTEN      0      128               [::]:22                            [::]:*

# 统计当前主机的连接状态信息
ss -tan | awk '!/State/{state[$1]++}END{for(i in state){print i,state[i]}}'
LISTEN 6
ESTAB 4
TIME-WAIT 2728

# 发现异常ip地址,进行杜绝恶意ip地址访问
# 第一个awk,分隔之后最后一个字段为空字符串,需要的ip在倒数第三个字段
ss -nt | awk -F'[ :]+' '!/State/{ip[$(NF-2)]++}END{for(i in ip){print i,ip[i]}}' | while read line; do ip=$(echo $line | awk '{if($2>1)print $1}');[ -z "$ip" ] || echo "iptables -A INPUT -s $ip -j REJECT"; done
iptables -A INPUT -s 192.168.91.1 -j REJECT

# 这里为了演示成功,故意将恶意ip的频率降低了
# 如果不小心真的添加了防火墙策略,则执行下面的命令实现功能恢复
iptables -vnL INPUT
iptables -D INPUT 1

# 实践2-web访问信息统计
# 获取客户端ip地址信息
awk -F '"' 'NR==404 {print $(NF-1)}' /var/log/nginx/access.log
114.101.40.170

awk -F '"' '{ip[$(NF-1)]++}END{for(i in ip){print i,ip[i]}}' /var/log/nginx/access.log
124.205.143.213 5538
125.46.0.62 1612
123.15.24.200 5200
114.101.40.170 4420
- 2
218.2.226.42 1092
2.2.2.2 1
223.243.252.155 4030
112.64.233.130 3594
122.228.19.92 2392

# 统计站点的访问页面信息
# awk '{a[$7]++}END{for(v in a)print v,a[v]}' /var/log/nginx/access.log | sort -k1 -nr | head -n5
awk '{a[$7]++}END{for(v in a)print v,a[v] | "sort -k1 -nr | head -n5"}' /var/log/nginx/access.log
/nihao 1
/92/ 1196
/62/ 806
/42/ 546
/213/ 2769

实践3-脚本信息统计

net.sh

#!/bin/bash
# 功能: 脚本统计主机网络信息

# TCP连接数量
TCP_Total=$(ss -s | awk '$1=="TCP"{print $2}')
# UDP连接数量
UDP_Total=$(ss -s | awk '$1=="UDP"{print $2}')
# Listen监听状态的TCP端口数量
Listen_Total=$(ss -antlpH | awk 'BEGIN{count=0} {count++} END{print count}')
# ESTABLlSHED状态的TCP连接数量
Estab_Total=$(ss -antpH | awk 'BEGIN{count=0}/^ESTAB/{count++}END{print count}')
# TIME-WAIT状态的TCP连接数量
TIME_WAIT_Total=$(ss -antpH | awk 'BEGIN{count=0}/^TIME-WAIT/{count++}END{print count}')

#显示主机连接相关信息
echo "TCP连接总数:$TCP_Total"
echo "UDP连接总数:$UDP_Total"
echo "LISTEN状态的TCP端口数量:$Listen_Total"
echo "ESTAB状态的TCP连接数量:$Estab_Total"
echo "TIME-WAIT状态的TCP连接数量:$TIME_WAIT_Total"
/bin/bash net.sh
TCP连接总数:10
UDP连接总数:2
LISTEN状态的TCP端口数量:6
ESTAB状态的TCP连接数量:4
TIME-WAIT状态的TCP连接数量:2627

文件实践

所谓的文件实践,主要是借助于awk的数组功能,实现文件的合并格式化等工作

查看日志的样式

默认日志格式
192.168.91.101 - - [27/Dec/2023:08:47:25 +0800] "HEAD /130/ HTTP/1.1" 404 0 "-" "curl/7.29.0" "112.64.233.130"
	
期望统计信息,第一个访问次数是ip的访问次数,第二个访问次数是指定ip的url访问次数
--------------------------------------------
|ip地址          |访问次数|访问url|访问次数|
--------------------------------------------
|  112.64.233.130|    3594|  /130/|    1797|
|  112.64.233.130|    3594|      /|    1797|
# 准备工作

# 获取ip地址
awk -F '("| )' 'NR==404 {print $(NF-1)}' /var/log/nginx/access.log
114.101.40.170

# 获取访问页面
awk -F '("| )' 'NR==404 {print $(NF-13)}' /var/log/nginx/access.log
/

# 输出统计信息
awk -F '("| )' '
  BEGIN{
    sep="--------------------------------------------\n"
    printf sep"|%-14s|%-4s|%-4s|%-4s|\n"sep,"ip地址","访问次数","访问url","访问次数"
  }
  # a["114.101.40.170"]["/"]
  {a[$(NF-1)][$(NF-13)]++}
  END{
    # 遍历数组,统计每个ip的访问总数
    for(ip in a){
      for(uri in a[ip]){
        b[ip] += a[ip][uri]
      }
    }
    # 再次遍历
    for(ip in a){
      for(uri in a[ip]){
        printf "|%16s|%8d|%7s|%8d|\n", ip,b[ip],uri,a[ip][uri]
      }
    }
    printf sep
  }
' /var/log/nginx/access.log
--------------------------------------------
|ip地址          |访问次数|访问url|访问次数|
--------------------------------------------
| 124.205.143.213|    5538|  /213/|    2769|
| 124.205.143.213|    5538|      /|    2769|
|     125.46.0.62|    1612|      /|     806|
|     125.46.0.62|    1612|   /62/|     806|
|   123.15.24.200|    5200|  /200/|    2600|
|   123.15.24.200|    5200|      /|    2600|
|  114.101.40.170|    4420|  /170/|    2210|
|  114.101.40.170|    4420|      /|    2210|
|               -|       2|      /|       1|
|               -|       2| /nihao|       1|
|    218.2.226.42|    1092|   /42/|     546|
|    218.2.226.42|    1092|      /|     546|
|         2.2.2.2|       1|      /|       1|
| 223.243.252.155|    4030|      /|    2015|
| 223.243.252.155|    4030|  /155/|    2015|
|  112.64.233.130|    3594|  /130/|    1797|
|  112.64.233.130|    3594|      /|    1797|
|   122.228.19.92|    2392|   /92/|    1196|
|   122.228.19.92|    2392|      /|    1196|
--------------------------------------------