shell编程之awk(数据筛选与处理)

shell编程之awk(数据筛选与处理)

  • awk 命令介绍
    • awk 语法
    • awk 的基本应用
      • awk 对字段(列)的提取
      • awk 对记录(行)的提取
      • awk 程序的优先级
    • awk 高级应用
      • -F命令选项
      • awk 定义变量(-v命令)
      • awk 定义数组
      • awk 运算
        • awk 比较预算:>、>=、==、<、<=、!=
        • awk 数学运算:+、-、\*、/、\*\*、%、++、+=、--、-=
        • awk 字符串匹配运算:==、!=、\~、!\~
        • awk 逻辑运算:&&、||
    • awk 环境变量
      • FIELDWIDTHS:以空格重新分隔字段
      • FS:指定列输入分隔符;OFS:指定列输出分隔符
      • RS:输入行分隔符
      • ORS:输出行分隔符
    • awk 流程控制-循环语句
      • awk if-流程控制
      • awk for-循环语句
      • awk while-循环语句
      • awk do..while循环语句
      • awk 流程控制-循环控制(break)

awk 命令介绍

常见的数据分析:

  • 数据数据收集:负责数据的收集

  • 数据清洗:负责数据的筛选

  • 数据分析:数据运算、整理

  • 数据展示:图表或表格方式输出结果

shell脚本数据的处理 :

  1. 数据检索:grep、tr、cut
  2. 数据处理:uniq、sort、tee、paste、xargs

之前的脚本都是通过grep、tr、cut、uniq、sort、tee、paste、xargs等命令通过管道组合在一起将字符串检索出来的,然后通过shell中对应的运算得到结果,在数据检索过程中操作比较繁琐

awk介绍:

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

内存使用率的统计步骤:

  1. 通过free -m 提取成内存总量,赋值给变量memory_totle

  2. 通过free -m 提取出n内存使用量,赋值给变量memory_use

  3. 通过数学运算计算内存使用率

操作过于繁琐,需要执行多步才能得到内存使用率,而awk命令能够集过滤、提取、运算为一体

awk命令可以从输出流中检索自己需要的数据,而不需要向以前一样通过大量命令组合检索。只需要一个awk命令就能完成,并且还能够通过awk对数据进行处理,而不需要额外的shell运算

awk命令是一种可以处理数据、产生格式化报表的语言,功能十分强大。awk认为文件中的每一行都是一条记录,记录与记录的分隔符为换行符每一列都是一个字段,字段与字段的分隔符默认是一个或多个空格或tab制表符

awk的工作是读取数据,将每一行数据视为一条记录,每条记录以字段分隔符划分若干个字段,然后输出各字段的值

awk的平行命令还有:gawk、pgawk、dgawk

总结: awk 是行编辑器,负责数据的截取和处理

awk 语法

语法格式:
#	awk [option] [BEGIN]{pogram}[END][filename]
		常用options选项:
			-F :指定描绘一行中数据字段的文件分隔符,默认为空格(即指定行的分隔符)
			-f file:指定读取程序的文件名,就是将awk的命令放到文本中用-f选项调用
			-v var=value:定义awk程序中使用的变量和默认值

#l	注意:awk程序脚本由左大括号和有大括号定义。脚本命令必须放置在两个大括号之间。
#l	     由于awk命令假定脚本是单文本字符串,所以必须将脚本包括在单引号内

#awk程序运行优先级是:
	1"BENGIN":在开始处理数据流前执行,可选项
	2"program":如何处理数据流,必选项
	3"END":处理完数据流后执行,可选项

awk 的基本应用

awk 对字段(列)的提取

字段提取: 提取一个文本中的一列数据并打印输出
用逗号,分隔字段内置变量打印

字段相关内置变量:

  • $0:表示整行文本
  • $1:表示文本行中的第一个数据字段
  • $2:表示文本行中的第二个数据字段
  • $N:表示文本行中的第N个数据字段
  • $NF:表示文本行中的最后一个数据字段
[root@server ~]# cat awk_column
	 1 This little sister is my dish
	 2 This little sister is my dish
	 3 This little sister is my dish
	root:x:0:0:root:/root:/bin/bash
	bin:x:1:1:bin:/bin:/sbin/nologin

#l 提取列(第一列)
[root@server ~]# awk '{print $1}' awk_column
1
2
3
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin

#l 提取列(第二列)
	[root@server ~]# awk '{print $2}' awk_column
		This
		This
		This
		//这是打印的空行
		//这是打印的空行

#l 指定分隔符提取列(第二列)
	[root@server ~]# awk -F ":" '{print $2}' awk_column
		//这是打印的空行
		//这是打印的空行		
		//这是打印的空行			
		x
		x

#l 打印第13列数据
	[root@server ~]# awk '{print $1,$3}' awk_column
		1 little
		2 little
		3 little
		root:x:0:0:root:/root:/bin/bash
		bin:x:1:1:bin:/bin:/sbin/nologin

#l 打印第1列和最后一列数据
	[root@server ~]# awk '{print $1,$NF}' awk_column	=>	第四五行只有一列,所以第一列和最后一列都是它自身
		1 dish
		2 dish
		3 dish
		root:x:0:0:root:/root:/bin/bash root:x:0:0:root:/root:/bin/bash
		bin:x:1:1:bin:/bin:/sbin/nologin bin:x:1:1:bin:/bin:/sbin/nologin

awk 对记录(行)的提取

记录提取: 提取一个文本的一行并打印输出

用逗号,分隔行号打印;NR==1,NR==3:打印1至3行
用冒号;分割行号awk程序;NR==1,NR==3:打印1和3行

记录的提取方法有两种:通过行号提取通过正则匹配

记录相关内置变量:

  • NR==N:指定行号N
[root@server ~]# cat awk_column
	 1 This little sister is my dish
	 2 This little sister is my dish
	 3 This little sister is my dish
	root:x:0:0:root:/root:/bin/bash
	bin:x:1:1:bin:/bin:/sbin/nologin


#l 提取行全部数据(第四行)
	[root@server ~]# awk 'NR==4{print $0}' awk_column
		root:x:0:0:root:/root:/bin/bash

#l 提取第四行第5个字段,-F 指定分隔符 ":"
	[root@server ~]# awk -F ":" 'NR==4{print $5}' awk_column
		root

#l 同时打印多列(第二行34列)
	[root@server ~]# awk 'NR==2{print $3,$4}' awk_column
		little sister

#L 打印多行,同时可以指定输出列的位置。(NR==1,NR==3:代表13行)		=>	, 逗号分隔行号
	[root@server ~]# awk 'NR==1,NR==3{print $6,$3,$4}' awk_column
		my little sister
		my little sister
		my little sister

#l	打印多行,分割awk程序											=>	;	冒号分割
[root@server ~]# awk 'NR==3;NR==3{print $1,$6,$3,$4}' awk_column
	 3 This little sister is my dish
	3 my little sister

awk 程序的优先级

关于awk程序执行的优先级,BEGIN是优先级最高的代码块,是在执行program之前执行,不需要提供数据源,因为不涉及到任何数据的处理,也不依赖于program代码块

program是对数据流操作,是必选代码块,也是默认代码块。所以在执行时必须提供数据源

END是处理完数据流后的操作,如果需要执行END代码块,就必须需要program的支持,单个无法执行

BEGIN 说明:

#l 不需要数据源就可以执行:
	[root@server ~]# awk 'BEGIN{print "hello"}'
		hello

程序优先级说明:

  • program需要数据源,END需要program处理后的数据源
[root@server ~]# awk 'END{print "end========"}{print $0}BEGIN{print "start========"}' awk_column
	start========
	 1 This little sister is my dish
	 2 This little sister is my dish
	 3 This little sister is my dish
	root:x:0:0:root:/root:/bin/bash
	bin:x:1:1:bin:/bin:/sbin/nologin
	end========

#l 备注:会优先执行BEGIN的内容,然后是program,再然后是END的内容

程序优先级总结:

BEGIN:处理数据源之前干什么,不需要数据源就可以执行
program:对数据源干什么,需要数据源(截取和运算)
END:对数据源处理后干什么,需要数据源

awk 高级应用

awk 是一本语言,那么就会符合语言的特性,除了可以定义变量外、还可以定义数组、还可以进行计算、还可以流程控制

-F命令选项

  • 重新指定默认分隔符空格,并将-F指定的分隔符删除打印
  • 内置变量如果不加逗号,隔开-F不会使用空格分隔符
#l	以冒号:为分割符打印13,最后一个字段,默认以空格为分隔符打印
	[root@server ~]# awk -F ":" 'NR==1{print $1,$5,$NF}' /etc/passwd
		root root /bin/bash

#l	可以通过指定字符串打印
	[root@server ~]# awk -F ":" 'NR==1{print $1"-"$5"-"$NF}' /etc/passwd
		root-root-/bin/bash
#l	没有逗号,不隔开
	[root@server ~]# awk -F ":" 'NR==1{print "uid:"$1"gid:"$5"shell:"$NF}' /etc/passwd
		uid:rootgid:rootshell:/bin/bash

#l	逗号隔开列内置变量,打印列时有空格分隔每个字段
	[root@server ~]# awk -F ":" 'NR==1{print "uid:"$1,"gid:"$5,"shell:"$NF}' /etc/passwd
		uid:root gid:root shell:/bin/bash

awk 定义变量(-v命令)

在程序里面定义变量需要用冒号;分割

#l -v 选项定义一个变量
	[root@server ~]# awk -v name='coco' 'BEGIN{print name}'
		coco

#l 在BEGIN里面定义变量
	[root@server ~]# awk 'BEGIN{name="liwen";print name}'
		liwen

#l 对数据源中的数据赋值给变量(计算totl-free得到使用的内存量)
	[root@server ~]# head -4 /proc/meminfo	=>	内存会发生变化
		MemTotal:        1863252 kB
		MemFree:          164712 kB
		MemAvailable:     555936 kB
		Buffers:               0 kB
	[root@server ~]# awk 'NR==1{t=$2}NR==2{f=$2;print t,f}' /proc/meminfo
		1863252 161420
	[root@server ~]# awk 'NR==1{t=$2}NR==2{f=$2;print (t-f)}' /proc/meminfo		=>	总体差不多,因为内存是动态的
		1701628

awk 定义数组

数组定义方式:数组名[索引]=值

  • 定义数组时用冒号;分割
#l 在BEGIN里面定义一个数组,取名a
	[root@server ~]# awk 'BEGIN{a[0]="干啥子";a[1]=5;print a[0],a[1]}'
		干啥子 5

#l 从数据源中将数据取出来赋值给数组,取名b
	[root@server ~]# cat array
		 1 This little sister is my dish
		 2 This little sister is my dish
	[root@server ~]# awk 'NR==2{b[0]=$1;b[1]=$4}NR==2{b[3]=$NF;print b[0],b[1],b[3]}' array
		2 sister dish

awk 运算

  • 注意在程序{program}内是无法进行运算的

awk 比较预算:>、>=、==、<、<=、!=

文件内容:

[root@server ~]# cat num
	1
	2
	3
	4
	5
	6
	7
	8
	9
	10
	aaa

awk 比较运算:(需要数据源的比较方式):

#l 				大于	>
	[root@server ~]# awk '$1>7{print $0}' num
		8
		9
		10
		aaa

#l				大于等于		>=
	[root@server ~]# awk '$1>=7{print $0}' num
		7
		8
		9
		10
		aaa
		
#l				等于	==			(注意不要和一个等于赋值搅浑)
	[root@server ~]# awk '$1==3{print $0}' num
		3

#l				小于	<		(aaa不见了)
	[root@server ~]# awk '$1<3{print $0}' num
		1
		2

#l				小于等于		<=
	[root@server ~]# awk '$1<=3{print $0}' num
		1
		2
		3

#l				不等于		!=
	[root@server ~]# awk '$1!="aaa"{print $0}' num
		1
		2
		3
		4
		5
		6
		7
		8
		9
		10

#l				赋值		=
	[root@server ~]# awk '$1="再见"{print $0}' num
		再见
		再见
		...全部都改变

awk 比较运算:(不需要数据源的比较方式):

  • 也可以做真假判断
  • 根据返回内容。0代表假,1代表真
#l	小于	
	[root@server awk]# awk 'BEGIN{print 3<4}'1
	[root@server awk]# awk 'BEGIN{print 4<3}'0

#l 大于,注意:一个大于相当于覆盖输入 无效
	[root@server awk]# awk 'BEGIN{print 4>3}'
	[root@server awk]# ll
		total 4
		-rw-r--r-- 1 root root 2 Jun  5 01:09 3	
	[root@server awk]# cat 3
		4

#l	大于等于
	[root@server awk]# awk 'BEGIN{print 3>=1}'1
	[root@server awk]# awk 'BEGIN{print 1>=3}'0
	
#l	小于等于
	[root@server awk]# awk 'BEGIN{print 4<=5}'1
	[root@server awk]# awk 'BEGIN{print 5<=4}'0

#l	等于等于
	[root@server awk]# awk 'BEGIN{print 4==4}'1
	[root@server awk]# awk 'BEGIN{print 4==3}'0

#l	不等于
	[root@server nmap]# awk 'BEGIN{print 4!=3}'1
	[root@server nmap]# awk 'BEGIN{print 4!=4}'0


#l	备注:一个等于(=)会报错,大于(>)覆盖输入

awk 数学运算:+、-、*、/、**、%、++、+=、–、-=

  • 默认支持浮点型。小数点精确到后四位
[root@server nmap]# awk 'BEGIN{print 2+3}'
	5
[root@server nmap]# awk 'BEGIN{print 2-3}'
	-1
[root@server nmap]# awk 'BEGIN{print 2*3}'
	6
[root@server ~]# awk 'BEGIN{print 100/3}'
	33.3333
[root@server ~]# awk 'BEGIN{print 1/10000}'
	0.0001
[root@server ~]# awk 'BEGIN{print 1/100000}'
	1e-05
[root@server nmap]# awk 'BEGIN{print 4%3}'
	1
[root@server nmap]# awk 'BEGIN{print 2**3}'
	8
[root@server nmap]# awk 'BEGIN{print (10.5+5.5)*2/4-1}'

	7

#l	++	+=
	[root@server ~]# awk 'BEGIN{a=1;print ++a}'
		2
	[root@server ~]# awk 'BEGIN{a=1;a+=1;print a}'
		2

#l	--	-=
	[root@server ~]# awk 'BEGIN{a=10;print --a}'
		9
	[root@server ~]# awk 'BEGIN{a=10;;a-=1;print a}'
		9

awk 字符串匹配运算:==、!=、~、!~

  • 还可以加入正则进行匹配
#L	精确匹配:	==
	[root@server nmap]# awk -F ":" '$1=="root"{print $0}' /etc/passwd
		root:x:0:0:root:/root:/bin/bash

	[root@server ~]# awk -F ":" '$1=="ro"{print $0}' /etc/passwd
		ro:x:1002:1002::/home/ro:/bin/bash



#l	精确不匹配
		[root@server ~]# awk -F ":" '$1!="root"{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
			....

#l	模糊匹配:字符为 ~		=>	即第一列有ro字符的匹配出来
		[root@server ~]# awk -F: '$1~"ro"{print $0}' /etc/passwd
			"ro"ot:x:0:0:root:/root:/bin/bash
			ch"ro"ny:x:992:987::/var/lib/chrony:/sbin/nologin
			set"ro"ubleshoot:x:989:983::/var/lib/setroubleshoot:/sbin/nologin
			ro:x:1002:1002::/home/ro:/bin/bash
	
		[root@server ~]# awk -F: '$1~"^ro"{print $0}' /etc/passwd		=>	加入正则
			root:x:0:0:root:/root:/bin/bash
			ro:x:1002:1002::/home/ro:/bin/bash



#l	模糊不匹配:字符为 !~		=>	即第一列有ro字符的不匹配
		[root@server ~]# awk -F: '$1!~"ro"{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
			lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
			...


#l	逻辑匹配:||,匹配第一列为root和lisi行
[root@server ~]# awk -F ":" '$1=="root"||$1=="lisi"{print $0}' /etc/passwd
	root:x:0:0:root:/root:/bin/bash
	lisi:x:1001:1001::/home/lisi:/bin/bash

awk 逻辑运算:&&、||

#l	与逻辑运算:全真为真,一假永假
	[root@server ~]# awk 'BEGIN{print 2>=1 && 3>=1}'1
	[root@server ~]# awk 'BEGIN{print 2>=1 && 3<1}'0


#l	与逻辑运算:一真永真,全假为假
	[root@server ~]# awk 'BEGIN{print 2>=1 || 3<1}'1
	[root@server ~]# awk 'BEGIN{print 2<=1 || 3<1}'0

awk 环境变量

变量 描述
FIELDWIDTHS 以空格分隔的数字列表,用空格定义每个数据字段的精确宽度
FS 输入字段分隔符号,与-F命令类似(数据源的字段分隔符)
OFS 输出字段分隔符号,打印时不能打印$0
RS 输入记录分隔符号,默认是回车
ORS 输出记录分隔符号 ,默认是回车,打印时不能打印$0

FIELDWIDTHS:以空格重新分隔字段

  • 重新定义列宽并打印,注意不可以使用$0打印所有,因为$0是打印本行所有内容,不会打印你定义的字段

  • BEGIN中定义

  • FIELDWIDTHS=“列宽值 列宽值 …”,列宽值代表打印出来的字符数量

  • 如果原文本本来就有空格,这个空格也算一个字符被重新定义

[root@server ~]# awk 'NR==1{print $0}' /etc/passwd
	root:x:0:0:root:/root:/bin/bash


#l	表示第一列$1的字宽为5,第二列$2的字宽为2,第三列$3的字宽为8。本行没有定义列宽是不打印的
	[root@server ~]# awk 'BEGIN{FIELDWIDTHS="5 2 8"}NR==1{print $1,$2,$3}' /etc/passwd
		root: x: 0:0:root


#l	提取13行,重新定义列宽
[root@server ~]# awk 'BEGIN{FIELDWIDTHS="3 5 4"}NR==1,NR==3{print $1,$2,$3}' /etc/passwd
		roo t:x:0 :0:r
		bin :x:1: 1:bi
		dae mon:x :2:2


#l	原文本有空格
[root@server ~]# awk 'BEGIN{FIELDWIDTHS="8 5 4 "}NR==1,NR==3{print $1,$2,$3}' sed.txt
	' 1 This ' 	"littl" 'e si'
	 2 This  littl e si
	 3 This  littl e si
	备注:引号包围的为一个字段

FS:指定列输入分隔符;OFS:指定列输出分隔符

  • FS:处理数据源前,指定数据源中字段分隔符,类似命令选项-F

    1. FS:就是告诉文件他的列分隔符是啥
  • OFS:指定输出到屏幕后字段的分隔符

    1. OFS:就是当文件输出时将它的分隔符转换成OFS指定的分隔符展示
  • 通过指定FS输入分隔符,删除文本的指定分隔符;然后指定OFS输出分隔符替换输出分隔符输出

  • FS;OFS:以分号隔开

#l	指定输入分隔符,打印 第一行和第三行中,以FS=":"指定的分隔符打印13,最后一列字段(与-F:一致)
	[root@server ~]# awk 'BEGIN{FS=":"}NR==1||NR==3{print $1,$3,$NF}' /etc/passwd
		root 0 /bin/bash
		daemon 2 /sbin/nologin
	[root@server ~]# awk -F: 'NR==1,NR==3{print $1,$3,$NF}' /etc/passwd
		root 0 /bin/bash
		bin 1 /sbin/nologin
		daemon 2 /sbin/nologin


#l	OFS替换FS输出分隔符
	[root@server ~]# awk 'BEGIN{FS=":";OFS=" - "}NR==1{print $1,$3,$NF}' /etc/passwd
		root - 0 - /bin/bash

RS:输入行分隔符

  • RS:指定记录的分隔符
  • 就是告诉文件它的行分隔符是什么,让它按RS指定的分隔符整理成新行
#l	RS="",表示告诉文件它的行分隔符不在是回车,而是空行,那么文件全部内容都在一行
		[root@server ~]# awk 'BEGIN{RS=""}{print $1}' num
			1
		[root@server ~]# awk 'BEGIN{RS=""}{print $1,$2,$3,$4,$5,$6,$7,$8,$9,$10}' num
			1 2 3 4 5 6 7 8 9 10

ORS:输出行分隔符

  • 输出到屏幕后记录的分隔符,默认为回车
#l	ORS=" ",表示将行的输出分隔符替换成空格,由原来的回车变成空格,
#l	所以将第一列的数据以空格分隔一行输出
	[root@server ~]# awk 'BEGIN{ORS=" "}{print $1}' num
		1 2 3 4 5 6 7 8 9 10 aaa [root@server ~]#

awk 流程控制-循环语句

循环时文件中每一行被awk调用时都是重新开始循环的。

awk if-流程控制

awk 单if 语句:

[root@server ~]# awk '{if ($1>7) print $0}' num
	8
	9
	10


[root@server ~]# awk '{
> if ($1<4)
> print $1
> }' num
	1
	2
	3

awk if..else语句:

[root@server ~]# awk '{if ($1>7) print $1/3 ;else print $1*2}' num
	2
	4
	6
	8
	10
	12
	14
	2.66667
	3
	3.33333


[root@server ~]#  awk '{
if ($1==10)
    print $1/10
else
    print ($1+100)
}' num
	101
	102
	103
	104
	105
	106
	107
	108
	109
	1

awk for-循环语句

awk for循环说明:

[root@server ~]# cat num2
	123 223 456
	122 544 23
	112 33 45


#l	没有事先定义变量sum的值,得出每行的数据是累前面所有行的总和和
awk: cmd. line:2:                ^ syntax error
[root@server ~]# awk '{
for (i=1;i<4;i++){
    sum+=$i
}
print sum
}' num2
	802
	1491
	1681

解释:因为变量sum是在for循环中定义的,当第一行for循环结束后,输出sum,这时的sun是第一行的和;
然后文件第二行被awk编辑,变量sum是上一行循环后的值,然后第二行的列数据继续往sum上加,
输出sum,这时的sum是第一行和第二行的和;然后以此类推,第三行输出的变量sum就是前三行的和



#l	利用awk for 循环计算num2中每一行的和
[root@server ~]# awk '{
>   sum=0
> for (i=1;i<4;i++){
> sum+=$i
> }
> print sum
> }' num2
	802
	689
	190

#l	备注:因为在循环外定义了sum=0,下一行进入循环就不会受到上一行循环变量sum的影响,输出值就是每行的和

awk while-循环语句

[root@server ~]# awk '{
> sum=0
> i=1
> while (i<4) {
>    sum+=$i
>    i++
> }
> print sum
> }' num2			
	802
	689
	190

awk do…while循环语句

  • 先执行后判断
[root@server ~]# awk '{
> sum=0
> i=1
> do {
>   sum+=$i
>   i++
> }
> while (i<4)
> print sum
> }' num2
	802
	689
	190

awk 流程控制-循环控制(break)

break 在for循环中跳出循环说明:

[root@server ~]# awk '{
> sum=0
> for (i=1;i<4;i++) {
>       sum+=$i
>       if (i==2)
>           break
> }
> print sum
> }' num2
	346
	666
	145
[root@server ~]# cat num2
	123 223 456
	122 544 23
	112 33 45



备注:上述是当文件中执行到每行的第二列字段时跳出循环,重新执行下一行。
	  因为if是在'sum+=$i'后,所以计算出来的数据是前两列字段的和

你可能感兴趣的:(shell编程)