文件三剑客之awk篇

一、awk基础部分

1.1读者对象

本文档主要针对了解awk的读者,如果你连awk都没有用过,那此文章不适合你。

1.2语法格式

语法格式: awk [options参数] 'commands' files

-F 定义字段分隔符,默认分隔符为连续空格或制表符。用$1,$2,$3等顺序表示files中每行以间隔符号分隔的各列不同域
-v 定义变量并赋值,也可以借用其他方式从shell变量中引入变量

FS 目前的分隔符,默认是空格键,单词field of split
NF 每一行($0)拥有字段的总数,单词number of field
$NF 每一行的最后一列
NR 目前awk所处理的是“第几行”数据 ,单词number of record

1.3awk有自己的特殊变量集合

$number  表示记录的字段。比如,$1表示第1个字段,$2表示第2个字段,如此类推。 而$0比较特殊,表示整个当前行.
NF 表示当前记录中的字段数量
NR 表示当前记录的编号(awk将第一个记录算作记录号1)
FS 表示字段分隔符

1.4BEGIN,END

BEGIN 模式指定了处理第一行文本之前需要执行的操作,

END 模式指定了处理完所有行之后所需要执行的操作。

BElinux是什么操作系统GIN的部分,常用于打印比如表格的第一列,题目列。或者声明一些变量,判断条件。END部分常用于打印表格的备注信息什么的。

1.5RS,ORS,FS,OFS部分

RS:Record Separator,记录分隔符,也有人叫行分隔符。
ORS:Output Record Separate,输出当前记录分隔符,可以理解为RS的逆向过程。
FS:Field Separator,字段分隔符。
OFS:Out of Field Separator,输出字段分隔符.表示输出字段分隔符,在两个单独的字段间插入定义的字符串

什么是field(字段),什么是record(记录行)?
示例:1.txt
1 i am a student.
2 i like to swim
3 hello moto

默认情况下:
1代表第一个记录行,
2代表第二个记录行,
3代表第三个记录行。通过观察我们可以知道总共有3个记录行(record)。

看看第一行:“i am a student”,这一行的每个单词都是一个字段(field)。“i”是一个字段,“am”是一个字段,“a”是一个字段,“student”是一个字段,该行总共有4个字段。

RS

RS:记录行分隔符,我们姑且就叫它块吧,块里面包含一个行,或者多个行。标准叫做记录分隔符。默认分割符\n
#RS虽然用的少,但是要知道。
#示例。从下面例子我们可以看出,竖线|作为记录分隔符,如果一行里面有竖线,那么一行的数据会被竖线分开成多行;所以也有人叫他行分隔符。
[root@node4 <sub>]# vi awk.txt
a|b|c

aa|bb

123
123|456 789
123| 456
888
[root@node4 </sub>]# awk 'BEGIN{ RS="|"; } { print $0 }' awk.txt
a
b
c

aa
bb

123
123
456 789
123
456
888
[root@node4 ~]# awk 'BEGIN{ RS="|"; } { print $0 }' awk.txt |wc -l
17

#RS也可以是正则表达式
[ocalhost test]$ echo "111 222a333 444b555 666"|awk 'BEGIN{RS="[a-z]+"}{print $1,RS,RT}'
111 [a-z]+ a
333 [a-z]+ b
555 [a-z]+

ORS

ORS:可以看成RS的逆向过程,默认值是\n
#下面例子可以看出,把文本的所有行组成一行了。常用于把所有内容组成一行的场景。
[root@node4 <sub>]# cat awk.txt
a|b|c

aa|bb

123
123|456 789
1010| 456
888
[root@node4 </sub>]# awk 'BEGIN{ORS="--"}{print $0}' awk.txt
a|b|c----aa|bb----123--123|456 789--1010| 456--888--

FS

FS:字段分隔符,就是你想把多个字段切开,总得有个分隔符。参数简写-F
FS默认值为“(空格)”, 如在“hello moto”中有一个空格,空格就是hello与moto的分隔符(separator),而hello与moto就为字段.

#FS也可以是正则表达式
[localhost test]$ echo "111||222|333"|awk 'BEGIN{FS="[|]+"}{print $1}'
111

#FS值并没有被限制为单一字符,可以通过指定任意长度的字符模式,将它设置成正则表达式。如果正在处理由一个或多个tab分隔的字段,可以按以下方式设置FS,在后面添加一个+
FS="\t+"

OFS

OFS:输出的字段分隔符。
个人理解:OFS就是逆向FS,是输出的时候用什么连接每个字段,即你把一个个字段分开,在输出的时候你要怎么把这些字段连接起来呢.
他们不是一个东西,可以在一起使用,
#例子
i am a teachar
i|am|very|beautiful
[root@node4 <sub>]# awk '{print $1}' awk.txt2 #默认是空格,或者tab键
i
i|am|very|beautiful
[root@node4 </sub>]# awk -F "|" '{print $1}' awk.txt2
i am a teachar
i
[root@node4 ~]# awk -F "|" 'BEGIN{OFS="---"}{print $1,$2}' awk.txt2 #FS,OFS同时使用。OFS是输出的时候,用什么连接每个字段。
i am a teachar ---
i---am

总结:

这个是我个人的总结,awkward翻译为了方便我自己的理解。

RS,记录分隔符,也有人叫行分隔符,常用于把linux删除文件命令一行分隔成多行。

ORS,输出当前记录分隔符,可以理awk用法解为RS的逆向过程。常用于把多行连接起来。

FS,字段分隔符,把一个个字段分开

OFS,输出字段分隔符,常用于把多个字段连接起来。

1.6表达式和运算符

#awk允许使用正则表达式,根据正则表达式是否匹配当前行来选择执行独立代码块。以下示例脚本只输出包含admin关键字的行:
awk -F : '/admin/{print $0}' /etc/passwd #输出包含admin的行

可以使用更复杂的正则表达式:
[root@node4 ~]# awk '/[0-9*]/{print $0}' awk.txt #包含0-9的数字任意1个
123
123|456 789
1010| 456
888

正则表达式元字符:

. 可代替除一行之外的任何1个字符(不能是0个)
* 可代替零个或多个在它前面出现的字符
[chars] 可代替chars中的任何一个字符,chars是一串字符序列。你可以用-符号来定义一个字符范围。如果^是chars中的第一个字符,那么将匹配没有在chars中指定的字符
^ 匹配一行的开头
$ 匹配一行的结尾
\ 把\后面的字符照常输出,通常用来转义(不使用特殊含义)一个元字符

除了使用正则表达式来选择行,我们也可以使用布尔表达式来选择行。使用方法是将布尔表达式放在代码块之前,仅当对awk命令前面的布尔表达式系统/运维求值为真时,awk才执行awk用法代码块

awk -F ':' '$1=="admin"{print $0}' /etc/passwd  #输出$1=="admin"的行。

awk 提供了完整的比较运算符集合,包括"=="、"<"、">"、"<="、">="和"!="。另外,awk还提供了"​"和"linux重启命令!​" 运算符,它们分别表示“匹配”和“不匹配”。它们的用法是在运算符左边指定变量,在右边指定正则表达式。例如:

awk -F ':' '(NR > 10)&&($1=="admin"){print $0}' /etc/passwd  #输出大于10行,用户名等于admin的行内容

awk的另一个优点是它有完整的数学运算符集合linux系统安装。除了标准的加、减、乘、除,awk还允许使用指数运算符"^"、模运算符"%"和其它许多从C语言中借入的易于使系统/运维用的赋值操作符。

这些运算符包括前后加减(i++、--j)、加/减/乘/除赋值运算符(a+=3、b*=2、c/=2.2、d-=6.2)。不仅如此,还有易于使用的模/指数赋值运linux删除文件命令算符(a^=2、b%=4)。

1.7if条件判断(命令行)

在awk 中,我们也是可以搭配if条件判断句 来使用,无非就是条件成立则执行对应的代码、条件不成立则不执行!
语法注意:
1、if外部要用{}括起来,里面的字符串用双引号括起来,用单引号报错。


[root@node4 <sub>]# awk -F ':' '{if($1=="admin"){print $0}else{print 888}}' /etc/passwd
888
admin:x:1000:1000::/home/admin:/bin/bash
[root@node4 </sub>]# awk -F ':' '{if($1=="admin"){print $0} else if($1=="admin2"){print $0} else{print 888}}' /etc/passwd
888
admin:x:1000:1000::/home/admin:/bin/bash
admin2:x:1001:1001::/home/admin2:/bin/bash

1.8for循环(命令行)

#for外部有个大括号。

[root@node4 ~]# echo "aa bb cc"|awk '{for(i=1;i<=NF;i++){print $i}}'
aa
bb
cc

1.9While 循环(命令行)

while (condition)
action
#While 循环首先检查条件 condition 是否为 true ,若条件为 true 则执行动作 action。此过程一直重复直到条件 condition 为 flase 才停止。

awk 'BEGIN {i = 1; while (i < 6) { print i; ++i } }'
#输出:
1
2
3
4
5

1.10 输出print,printf

[root@node4 ~]# awk -F ':' '{if($1=="admin"){print $0} else if($1=="admin2"){print $0} else{printf 888}}' /etc/passwd
88888888888888888admin:x:1000:1000::/home/admin:/bin/bashadmin2:x:1001:1001::/home/admin2:/bin/bash

从上面我们可以判断出print输出后换行,printf输出后不换行。

1.11 格式化输出

awk支持输出的时候,每个字段输出多少长度,是什么数据类型,是整数还是小数,最后的小数0是否要去掉,或者补充。

#默认的shell也支持printf函数:

[root@node3 <sub>] # printf "%s %d" aa 55
aa 55
[root@node3 </sub>] # printf "%G\n" 1.30
1.3
[root@node3 <sub>] # printf "%f\n" 1.3 #和上面精度不一样
1.300000
[root@node3 </sub>] # printf "%s %E\n" abc 1.01 #支持科学计数法
abc 1.010000E+00
awk支持printf函数:
[root@node3 <sub>] # awk -F: '{printf "%s\n",$1}' /etc/passwd #\n表示换行,单词new line
root
bin
daemon
adm
[root@node3 </sub>] # awk -F: '{printf "%-20s %-3s %-1s\n",$1,$2,$3}' /etc/passwd #$1对应"%-20s",$2对应"%-3s",$3对应"%-1s\n",
[root@node3 ~] # awk -F: '{printf "%-10s %-1s %s\n",$1,$2,"cc"}' /etc/passwd
root x cc
bin x cc
daemon x cc

"% s"
%和s是固定格式
-表示左对齐默认右对齐
20表示显示字符不足20个用空格补全到20个字符

%-5.2f 被称为格式符(format specifier), 格式符由以下几部分组成:
%[flags][width][.precision]conversion
% - 5 .2 f

###
%是必须的, 任何格式符都由百分号开始
flags 是可选的,详见下表
width 是可选的, 表示输出的宽度
precision 可选的, precision 依赖于 conversion, 详见下表.
conversion 是必须的, 表示如何格式化参数, 详见下表

#awk 支持如下标志(flag)
标志 描述 举例
- 左对齐 |3333.33 |
空格 在正数之前添加空格 | 3333.33|,|-3333.33|
+ 打印正负数符号 |+3333.33|,|-3333.33|
0 数字前面补0 |003333.33|
#(对于%o) 添加前缀0 |0515|
#(对于%x) 添加前缀0x |0x1bc|
#(对于%X) 添加前缀0X |0X1bc|
#(对于%e) 添加小数点 |1.000000e+01|
#(对于%E) 添加小数点 |1.000000E+01|
#(对于%f) 添加小数点 | 10.000000|
#(对于%g) 不删除尾部0 |10.4000|
#(对于%G) 不删除尾部0 |10.4000|

#awk 支持如下转换符(conversion)
转换符 描述
c ASCII 字符 (打印第一个字符)
d 十进制整数
i 十进制整数
e 浮点数科学计数法
E 浮点数科学计数法
f 浮点数
g %e 或 %f, 取决于哪个更短, 删除尾部0
G %E 或 %f, 取决于哪个更短, 删除尾部0
u 无符号十进制整数
o 无符号八进制整数
x 无符号十六进制整数(a-f for 10 to 15
X 无符号十六进制整数(A-F for 10 to 15
%% %
s 字符串

#awk 精度(precision)的意义:(这个精度很重要的,经常会用到额)
转换符 精度意义
%d,%i,%o,%u,%x,%X 最少数字位数,如果数字位数少于精度,添加前缀0
%e, %E 最少数字位数,如果数字位数少于精度,添加后缀0
%f 小数的位数
%g, %G 最多数字位数
%s 字符位数

1.12 字符串相关内置函艾万可吃一颗能做多久

这几个是常用的,返回字符串长度、大小写转换,匹配子字符串、替换字符串、切割字符串。awkward翻译

awk把所有变量都当作字符串处理,那么字符串处理对awk就显得尤为重要。awk有许多字符串函数,弥补了这个缺陷。现将常用的字符串函数列举如下:
字符串函数 描述
length() 返回字符串的长度,如果不写字符串,打印行的长度。
index() 返回子字符串在另一个字符串中出现的位置,返回下标起始位置。
tolower() 返回字符串并且将所有字符转换成小写
toupper() 返回字符串并且将所有字符转换成大写
substr() 返回从字符串中选择的子串,返回子字符串。
match() 返回子字符串在另一个字符串中出现的位置。它与index()的区别在于它并不搜索子串,它搜索的是正则表达式。
sub() 替换匹配的第一个字符序列,并返回整个字符串
gsub() 替换匹配的全部字符序列,并返回整个字符串
split() 分割字符串,并将各部分放到使用整数下标的数组中
-----------------------------------

[root@node3 <sub>] # awk -F : '{print length($1)}' /etc/passwd #length()返回字符串的长度
4
3
[root@node3 </sub>] # awk 'BEGIN{mystr="aabbcc025"}{print length(mystr)}' /etc/passwd #length()返回字符串的长度
9
9
[root@node3 <sub>] # awk -F : '{print length()}' /etc/passwd #打印每一行的长度
31

#index()返回子字符串在另一个字符串中出现的起始位置,下标是1开始,如果没有找到该字符串则返回0.返回下标起始位置。
[root@node3 </sub>] # awk -F: '{print index($1,"root")}' /etc/passwd
1
0
7

#tolower()和toupper()返回字符串并且将所有字符分别转换成小写或大写。注意,tolower()和toupper()返回新的字符串,不会修改原来的字符串。
[root@node3 <sub>] # awk -F: '{print toupper($1)}' /etc/passwd
ROOT
BIN
DAEMON

#substr()根据下标和长度,返回子字符串。substr($1,1,3)表示$1字段,1表示下标是1(默认从1开始),3表示长度。substr(mystring,开始下标,长度)
[root@node3 </sub>] # awk -F: '{print substr($1,1,3)}' /etc/passwd
roo
bin

#match()与index()非常相似,它与index()的区别在于它并不搜索子串,它搜索的是正则表达式。
match()函数将返回匹配的起始位置,如果没有找到匹配,则返回0;此外,match()还有两个变量(可写可不写),叫作RSTART和RLENGTH。RSTART包含返回值(第一个匹配的位置),RLENGTH指定它占据的字符跨度(如果没有找到匹配,则返回-1)。
[root@node3 <sub>] # awk -F: '{print match($1,/ot/),RSTART,RLENGTH}' /etc/passwd
3 3 2
0 0 -1
0 0 -1
[root@node3 </sub>] # awk -F: '{print match($1,/oot/)}' /etc/passwd
2
0

#sub()和gsub()是两个字符串替换函数;实现类似sed的替换功能。
#sub匹配第一次出现的符合模式的字符串,相当于 sed 's//' 。
#gsub匹配所有的符合模式的字符串,相当于 sed 's//g' 。
sub(regexp表示要替换的字符串,replstring用这个字符串去替换,mystring) #mystring可以不写,会替换所有出现的,如果写某个字段,就只替换某个字段
调用sub()时,它将在mystring中匹配regexp的第一个字符序列,并且用replstring替换该序列。gsub()与sub()的唯一的区别是sub()替换第一个regexp匹配(如果有的话),gsub()则执行全局替换,换出字符串中的所有匹配。举例如下:
[root@node3 <sub>] # echo "a b c 2011-11-22 a:d" | awk 'gsub(/-/,"",$4)' #只替换其中的某个字段
a b c 20111122 a:d
[root@node3 </sub>] #
[root@node3 <sub>] # echo "a b c 2011-11-22 a:d" | awk 'gsub(/-/,"")' #只替换所有出现的字符-,替换为空
a b c 20111122 a:d

[root@node3 </sub>] # awk -F : 'gsub(/root/,888){ print $0}' /etc/passwd
888:x:0:0:888:/888:/bin/bash
docker888:x:998:995:Docker User:/var/lib/docker:/sbin/nologin
[root@node3 <sub>] # awk -F : '{print gsub(/root/,888)}' /etc/passwd #这么写其实是打印替换了几次,而不是输出替换后的行内容。
3
0

[root@node3 </sub>] # cat data.test
0001|20081223efskjfdj|EREADFASDLKJCV
0002|20081208djfksdaa|JDKFJALSDJFsddf
[root@node3 <sub>] # awk -F '|' '{gsub(/[0-9]+/,"");print $0}' data.test #把文本中的数字替换为空
|efskjfdj|EREADFASDLKJCV
|djfksdaa|JDKFJALSDJFsddf

#split()的功能是分割字符串,并将分割后的各部分放到使用整数下标的数组中。
#此函数有三个变量,第一个自变量为要分割的原始字符串,第二个自变量为分割后填入的数组名称,第三个变素为用于指示分割的分隔符。
split()返回时,它将返回分割的字符串元素的数量。
split()将分割的每一个部分赋值给下标从1开始的数组。
[root@node3 </sub>] # mystr="11,22,33"
[root@node3 ~] # echo $mystr | awk '{split($mystr,mynum,",");print mynum[1],mynum[2]}'
11 22

1.13 exit

Exit 用于结束脚本程序的执行,后面可以跟一个状态码。

exit 0

exit 1

1.14 常用awk命令汇总

awk -F ':' '$1=="admin"{print $0}' /etc/passwd  #输出$1=="admin"的行。
awk -F ':' '{if($1=="admin"){print $0}}' /etc/passwd #同上,admin不能用单引号包围,有if判断,if最外部要加大括号
awk -F ':' '{if($1=="admin"){print $0} else{print 888}}' /etc/passwd #这里多加个else。
awk -F ':' '{if($1=="admin"){print $0} else if($1=="admin2"){print $0} else{print 888}}' /etc/passwd #多加个else if

awk -F ':' '(NR > 10)&&($1=="admin"){print $0}' /etc/passwd #输出大于10行,用户名等于admin的行内容
awk -F : '/admin/{print $0}' /etc/passwd #输出包含admin的行
awk -F : '/admin/{print "name:"$1,"id:"$2,"groupid:"$3}' /etc/passwd #每个字段前面加描述说明


[root@node4 ~]# awk -F : 'BEGIN{print 888}/admin/{print "name:"$1,"id:"$2,"groupid:"$3}END{print 999}' /etc/passwd
888
name:admin id:x groupid:1000
name:admin2 id:x groupid:1001
999

awk -F":" '/\/bin\/bash/{print $1}' /etc/passwd #表示只匹配到"bin/bash"的,才处理
awk -F":" '!/\/bin\/bash/{print $1 $7}' /etc/passwd #加个!号取反,匹配到"bin/bash"的不处理,其他都处理
awk -F : '/test/||/root/{print $0}' /etc/passwd #匹配root或者test
awk -F":" '/\/bin\/bash/||/\/bin\/csh/{print $1 $7}' /etc/passwd

1.15 常见场景

1统计文件行数

[root@scott ~]# awk 'END{print NR}' file 
40

2通过ipconfig,ip addr显示出IP

ifconfig |awk 'NR==2{split($2,a,":");print a[2]}'
ip a | awk 'NR==8 {print $2}' | awk -F '[/]' '{print $1}'

3在passwd文件最前和最linux必学的60个命令后一行添加

awk ' BEGIN {print "******<strong>"} {print  $0} END {print "</strong>******"}' passwd

4统计tcp各状态的连接数

[root@zabbix-test121 wz]#  netstat -ant|grep [0-9] |awk ' {S[$NF]++}END{for(k in S) print S[k],k}'
18 TIME_WAIT
4 ESTABLISHED
11 LISTEN

5统计日志中针对单个IP访问内容的统计

cat app.access.log | grep "218.17.206.10" | awk '{print $7}'   | awk '{S[$NF]++} END {for(k in S)print S[k],k}' | sort -nr | head -n 100

17684 /smtapp/openPlatform/initCode/getInitCode.do
15979 /smtapp/openPlatform/authCode/getAuthCode.do
15978 /smtapp/openPlatform/userInfo/getUserInfo.do

6读取某行到某行的内容

awk 'NR >=1 && NR<=3' awk.txt
另外一种方法就是sed命令:sed -n '1,3p' awk.txt -n表示只显示打印的内容,模式空间的其他值不显示。
还有一种方法是grep -A -B参数

用来用去还是编辑文件三剑客命令。

7awkward判断奇数偶数

awk 'BEGIN {num = 10; if (num % 2 == 0) printf "%d 是偶数\n", num }'

8 统计某个字段的次数,

通常用于统计某个ip地址,用户名等出现了多少次。awkward翻译

awk -F ':' '{S[$1]++}END{for(k in S) print S[k],k}' /etc/passwd |sort -rn|head

1.16 参考文章:

本文很多内容参考下面的文章,他们说的可能也详细,我这里只是记录,总结一下,记录我自己学习的内容。

​​

​​https://www.jianshu.com/p/b9fc78e530b2​​

​​

二、awk高级部分

awk写的命令,可以写在一个文件里面,然后调用这个文件,-f参数,假如你写的awk很长很长,里面包括if提交判断,for循环你就可以这么写,写到一个文件看起来规整,也是很好的处理方式。这部分内容算作awk的高级能内容了。

2.1可以参考下面两篇文章:

​​

​​https://blog.51cto.com/bubblawkward的意思e/117911linux系统5​​

​​​​

​​https://blog.51cto.c系统/运维om/系统运维是干嘛的bubble/1179134​​

​​

简单的写在txt文件的示例:

[root@node4 <sub>]# awk -f hello.awk /etc/passwd
admin
[root@node4 </sub>]# vi hello.awk
BEGIN {
FS=":"
}

(NR > 10)&&($1=="admin"){print $0} #注意,这里如果写在命令行的时候要加单引号

2.2字符串化变量

awk变量“字符串化”是指所有awk变量艾万可吃一颗能做多久在内部都是按字符串形式存储的。而且只要变linux必学的60个命令量包含有效数字字符串,就可以对它执行数学操作,awk会自动处理字符串到数字的转换步骤。请看以下这个示例:

[root@node4 <sub>]# cat awk.txt
a|b|c

aa|bb

123
123|456 789
1010| 456
888
[root@node4 </sub>]# cat awk.txt2 #统计空格的行数
BEGIN {x="0"}
/^$/ {x=x+1}
END {print x}
[root@node4 <sub>]# awk -f awk.txt2 awk.txt #统计空格的行数
2

[root@node4 </sub>]# vi awk.txt2 #统计包含aa字段有多少行,类似sort|uniq -c
BEGIN {x="0"}
/aa/ {x=x+1}
END {print x}
[root@node4 ~]# awk -f awk.txt2 awk.txt #统计包含aa字段有多少行,类似sort|uniq -c
1

2.3if条件语句(写在txawkwardlyt文件)

awk的if语句类似于C语言的if语句,举例。

[root@node4 <sub>]# cat awk.txt2
BEGIN{FS=":"}
{
if($1=="admin"){
print 111
}else if($1=="admin2"){
print 222
}else{
print 333
}
}
[root@node4 </sub>]# awk -f awk.txt2 /etc/passwd
333
333
111
222

2.4for循环(写在txt文件)

[root@node4 <sub>]# vi awk.txt2 
{
for(i=1;i<=NF;i++){print $1}
}
[root@node4 </sub>]# awk -f awk.txt2 awk.txt
a|b|c
aa|bb
123
123|456
123|456
1010|
1010|
888

2.5Break,linux系统安装continue

这里我就不说了,continue是进入下一次循环,break是结束循环。自己可以百度,和c语言都一样意思。

2.6数组

这里也不说了,知道数组可以实现就行,遇到要用数组的就百度。

三、最后总结

学到这里,其实我们最后发现awk其实就是一个编程语言linux必学的60个命令,可以实现:

  • 格式化输出,条件判断,for循环,break,continue,数组等功能;
  • 同时它还支持内置函数,比如大写转换,数linux系统安装学运算,加减乘除求余数等等;
  • 替换字符串,计算字符串的长度,分割字符串

最后我们如awkward果在工作中能用到awk的地方,多去用,多写就熟悉了。