文本处理三剑客3-Awk
awk的用户使用指南
相关链接文章:
正则表达式
文本三剑客1-grep
文本三剑客2-sed
1.总结对比一下这三个剑客的特长之处
grep、sed、awk被称为linux中的三剑客,区别在于:
1.复杂度:
grep更适合单纯的查找或匹配文件.
sed更适合编辑皮匹配到的文本
awk更适合格式化文本,对文本进行比较复杂格式处理
2.是否对文本进行修改:
文本三剑客都是默认逐行处理,自带循环
sed可以对文本进行修改,而grep和awk只是对文本进行过滤,不进行内容修改
2.写在前面的问题:
1.awk中的-F后面的分隔符,要用单引号'',双引号""会产生弱引用,比如特殊符号'\\\\' '\\\$'
关于awk中的单引号和双引号的问题参照:
[awk中的输入分隔符单引号&双引号](https://blog.csdn.net/swinfans/article/details/82991077)
2.学习awk的一个重要知识点
先举两个例子:
awk '/^UUID/' /etc/fstab = awk '/^UUID/{print $0}' /etc/fstab
其实是隐藏了{print $0}的写法
数组中的例子
awk '!line[$0]++' f6 = awk '!line[$0]++{print $0}' f6
实际上是隐藏了{print $0}
3.学习中遇到的混淆的问题:
'!line[$0]++'= '!line[$0]++{print $0}'是省略了写法,是patter的关系表达式中,先判断再打印
而后面的数组里的是加了双括号{},即用的patter的空模式,即文本中的每一行都处理
文章学习awk的步骤
基本用法和功能以及各个功能示例:
awk介绍
awk基本用法
awk变量
awk格式化-printf
awk操作符
awk条件判断
awk循环
awk数组
awk函数
调用系统命令
1.awk基本用法
1.命令路径
~]# which awk && ll /usr/bin/awk
/usr/bin/awk
lrwxrwxrwx. 1 root root 4 Mar 7 2019 /usr/bin/awk -> gawk
可以看出which awk=/usr/bin/awk是gawk的软链接
2.awk基本语法
awk [options] 'program' var=value file…
awk [options] -f programfile var=value file…
awk [options] 'BEGIN{action;… }pattern{action;… }END{action;… }' file ...
awk程序可由:BEGIN语句块、能够使用模式匹配的通用语句块、END语句块,共3部分组成、program通常是被放在单引号中
选项:
-F “分隔符” 指明输入时用到的字段分隔符
-v var=value 变量赋值
基本格式:awk [options] 'program' file…
Program:pattern{action statements;..}
也就是说awk用法:awk [options] 'pattern{action statements;..}' file…
pattern和action
• pattern部分决定动作语句何时触发及触发事件
BEGIN,END
• action statements对数据进行处理,放在{}内指明
print, printf
分割符、域和记录
• awk执行时,由分隔符分隔的字段(域)标记$1,$2...$n称为域标识。$0
为所有域,注意:此时和shell中变量$符含义不同
• 文件的每一行称为记录
• 省略action,则默认执行 print $0 的操作
print格式:print item1, item2, ...
要点:
(1) 逗号分隔符
(2) 输出item可以字符串,也可是数值;当前记录的字段、变量或awk的表达式
(3) 如省略item,相当于print $0
3.用法解析及示例:
$0=代表处理的整行的内容
$1,$2,$3..代表每一列,也就域
BEGIN,END是为生成一个报表的头和尾准备的,用法通常为:
BEGIN:为生成报告模式 添加表头;END:为生成的报告 进行信息汇总
awk 'BEGIN{print xxx}{print xxx}END{print xxx}'
注意:BEGIN{print *xxx}处理文本前,打印一遍xxx的内容当成表头
END{print xxx},处理文本后,打印一遍xxx的内容作为表尾
4.BEGIN&END
BEGIN:让用户指定在第一条输入记录被处理之前所发生的动作,通常可在这里设置全局变量。
END:让用户在最后一条输入记录被读取之后发生的动作。
5.分隔符:
awk默认使用空白符作为字段或列的分隔符;多个空白符被认为是一个,空白符包括空格,tab键/t,回车\n,也可以自定义-F"分隔符"自定义分隔符
6.print&printf的区别:
print命令只是单纯的把特定的内容进行打印,默认换行
printf命令可以把内容以灵活的格式进行显示,如左对齐,右对齐
7.示例
1.awk支持标准输入输出,后面可以不跟文件
~]# awk '{print $0}'
aaaa
aaaa
abcabc
abcabc
2.打印/etc/passwd:对比几个输出结果
awk '{print $0}' /etc/passwd #把passwd文件全部打印出来
awk '{print "abc"}' /etc/passwd #读入的是passwd文件所有行,打印的是abc
awk -v abc=1 '{print abc}' /etc/passwd
#读入的是passwd文件所有行,打印的都是1
awk '{print "abc"$0}' /etc/passwd
#把passwd文件所有行前都加一个abc,进行输出
所以在awk中不加双引号,abc被识别为变量,如果要引用变量,需要-v先定义值,如果只是想输出abc字符串,需要加双引号
3.awk{}中支持数字运算
~]# awk '{print 1+2}' /etc/passwd 打印多行1+2=3的值
~]# awk '{print "1+2"}' /etc/passwd 加双引号会把1+2当成字符串,输出多行1+2
4.取分区利用率df,
~]# df |awk '{print $1,$5}'| awk -F"%" '{print $1}'
5.以输入重定向方式,打印 passwd 文件的第1列,即获取当前系统中存在的所有用户和UID
~]# awk -F: '{print $1,$3}' < /etc/passwd
~]# cat /etc/passwd | awk -F: '{print $1,$3}'
~]# awk -F: '{print $1:$3}' /etc/passwd
#两列输出时,指定以:进行隔开,默认为空格隔开
~]# awk -F: '{print $1、$3}' /etc/passwd
#两列输出时,指定以:进行隔开,默认为空格隔开
~]# cat /etc/passwd | awk -F: '{print $1"\n" $3}' #两列输出时,进行换行
~]# cat /etc/passwd | awk -F: '{print $1"\t" $3}' #两列输出时,以tab键隔开
备注:
多行输出时,可以在双引号之间加自定义的分隔符
格式:awk -F: '{print $1"======="$3}' /etc/passwd
2.awk中的变量(变量又可分为内置变量和自定义变量)
2.1.awk的内置变量
awk中的内置变量除了$0,$1,$2等,还有以下几种,如果要使用这些变量需要加-v 选项先进行定义:
1.FS:输入字段分隔符,默认为空白字符 =filed separator=域或列的分隔符等于-F的选项,-F是选项,而FS是变量,实际作用是相等的与-F的区别在于:可以下次调用FS变量
~]# awk -v FS=':' '{print $1,$3}' /etc/passwd = awk -F:'{print $1,$3}' /etc/passwd
~]# awk -v FS=':' '{print $1,FS$3}' /etc/passwd
#两列输出时以:做分隔符,调用变量FS
~]# awk -v FS=':' '{print $1,FS FS$3}' /etc/passwd
#两列输出时以::做分隔符,调用2次变量FS 以空格隔开
可以先定义shell中的变量fs=:,awk再进行调用
~]# fs=:;awk -v FS=$fs '{print $1,FS,$3}' /etc/passwd
~]# fs=:;awk –F$fs '{print $1,$3,$7}' /etc/passwd
2.OFS:输出字段分隔符,默认为空白字符 =output filed separator定义输出分隔符,不指定默认空空格做分隔符
~]# awk -v FS=: -v OFS=+++ '{print $1,$3,$7}' /etc/passwd
~]# fs=+++;awk -v FS=: -v OFS=$fs '{print $1,$3}' /etc/passwd
#调用shell变量做输出分隔符
3.RS:输入记录分隔符,指定输入时的换行符,自定义行的分隔符 =record记录默认一行叫记录,行是以\n回车作为切割符的,RS可以自定义不用回车作为分隔符
~]# awk -v RS=' ' ‘{print }’ /etc/passwd
~]# awk -v RS=':' '{print NR,$0}'/etc/passwd
~]# cat f1
aa;xxx:bb;bzzzz:cc
dd:eex;zccc:xxxx
~]# cat f1| awk -v RS=":" '{print $0}'
aa;xxx
bb;bzzzz
cc
dd
eex;zccc
xxxx
以RS=:冒号自定义行的分隔符,输出结果如上
~]# cat f1| awk -v FS=";" -v RS=":" '{print $1}'
aa
bb
cc
dd
eex
xxxx
自定义FS&RS,输出结果如上
4.ORS:输出记录分隔符,输出时用指定符号代替换行符
~]# awk -v RS=' ' -v ORS='###'‘{print }’ /etc/passwd
~]# cat f1
aa;xxx:bb;bzzzz:cc
dd:eex;zccc:xxxx
~]# cat f1| awk -v FS=";" -v RS=":" -v ORS"===" '{print $1}'
aa==bb==cc
dd==eex==xxxx
==
可以看出自定义FS,RS,ORS结果很明显
5.NF是一个比较重要的变量,它是指字段数量,也就是域或列的总数量
~]# awk -F: '{print NF}' /etc/passwd
#以冒号做分隔符,显示每行的列的总数量
~]# awk -F: '{print $NF}' /etc/passwd
#显示以冒号做分隔符,每一行的最后一个字段即bash类型
~]# awk -F:'{print $(NF-1)}' /etc/passwd
#显示以冒号做分隔符,每一行的倒数第二个字段
示例:统计光盘中所有安装包适用的cpu架构类型
~]# ls /mnt/Packages/*.rpm | awk -F"." '{print $(NF-1)}'|sort|uniq -c
1371 noarch
2600 x86_64
6.NR:记录号,可以显示行数,如果有多个文件会合并后再统计总行
~]# awk '{print NR,$0}' /etc/fstab 在每一行前加一个行号
~]# awk BEGIN'{print NR}' /etc/fstab
#输出结果为0,因为awk还没开始处理行,所以记录为0
~]# awk END'{print NR}' /etc/fstab
#输出结果为12,可以看出END是统计,awk处理的行数
区别示例:1.通过加行号,可以很明显看出以冒号作为行的分隔符,每一行的都有什么;可以看出cc dd是一行的
~]# cat f1
aa;xxx:bb;bzzzz:cc
dd:eex;zccc:xxxx
~]# cat f1| awk -v RS=":" '{print NR$0}'
1aa;xxx
2bb;bzzzz
3cc
dd
4eex;zccc
5xxxx
示例2:如果awk命令后跟了两个文件,awk会把两文件合并成一个文件,统计总行数,都取第一个字段的信息,如果需要分开显示统计,则用FNR
~]# awk -F: '{print NR,$1}' /etc/passwd /etc/group
1 root
2 bin
3 daemon
4 adm
7.FNR:各文件分别计数,记录号,如果是多个文件,则把每个分别统计显示第一个字段并列出来
~]# awk '{print FNR}' /etc/fstab /etc/inittab
~]# awk -F: '{print FNR,$1}' /etc/passwd /etc/group
1 root
2 bin
3 daemon
48 quagga
49 httpd
1 root
2 bin
8.FILENAME:当前文件名
使用场景:统计时,加上变量可以显示文件名
语法:awk '{print FILENAME}' /etc/fstab
~]# awk -F: '{print FILENAME,FNR,$1}' /etc/passwd
/etc/passwd 1 root
/etc/passwd 2 bin
/etc/passwd 3 daemon
9.ARGC:命令行参数的个数
~]# awk '{print ARGC}' /etc/fstab /etc/inittab 结果为3
~]# awk 'BEGIN {print ARGC}' /etc/fstab /etc/inittab 结果为3
ARGC统计参数:awk单独算一个参数,后面的每个文件算一个参数,通过下面的数组可以体现出来
10.ARGV:数组,保存的是命令行所给定的各参数
示例:显示awk的每个参数分别是哪个
~]# awk '{print ARGV[0]}' /etc/fstab /etc/inittab
awk
~]# awk '{print ARGV[1]}' /etc/fstab /etc/inittab
/etc/fstab
2.2.awk的自定义变量
自定义变量(区分字符大小写)
(1) -v var=value
(2) 在program中直接定义
(2-1)program可以放到一个文本里,awk -f 直接调用即可
示例1:放在program中直接定义
自定义变量可以-v var定义,也可以放到program即{}里,变量要先定义,后使用
awk -F: '{name="mysql";print $1,name}' /etc/passwd
awk -F: -v name="mysql" '{print $1,name}' /etc/passwd
示例2:将自定义变量放在文本中
~]# cat awk.txt
{print $1,$2,$6} #以这个文本内容为自定义变量分隔符
~]# awk -F: -f awk.txt /etc/passwd =awk -F: '{print $1,$2,$6}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
注:因为/etc/passwd中没有{print $1,$2,$6}内容所以将/etc/passwd中每一行数据显示了
其他生产常用或面试示例:
1.统计当前网络连接情况的ip地址是 ss -nt
~]#ss -nt | awk '{print $5}'
2.取/var/log/httpd/access_log的时间如下:
~]# cat /var/log/httpd/access_log #文本内容格式
192.168.34.1 - - [15/Oct/2018:22:02:35 +0800] "GET / HTTP/1.1" 403 4897 "-"
方法1分两步取:
~]# cat /var/log/httpd/access_log | awk '{print $4}'|awk -F"[" '{print $2}'
方法2一步取:
~]# cat /var/log/httpd/access_log | awk -F "[\[ ]" '{print $5}'
逻辑分析:
[\[ ]代表(转义)\[和空格都算是分隔符,正则表达式的写法,或的关系,
而以空格分隔符,时间段为$4,那为什么是$5?在空格的标准里,[算$4,所以时间段为$5
3.取出磁盘分区利用率 -这次只取出利用率
方法1两步取出:
~]# df | awk -F% '{print $1}'|awk '{print $5}'
方法2一步取出:
~]# df | awk -F"[[:space:]]+|%" '{print $5}'
#awk用的是扩展的正则表达式
4.面试题:取出fstab中挂载的目录
~]# cat /etc/fstab
UUID=9612633f-e7f1-4b28-8813-403d209d7abc / xfs defaults 0 0
UUID=0eba9e52-43a4-4c64-89bd-3fb639f0a6c1 /boot xfs defaults 0 0
UUID=06fe63d1-9b57-436f-ad7d-1c01c7a60aee /data xfs defaults 0 0
UUID=48e92f06-6fcb-4a21-8ba0-e7d1c5c1af39 swap swap defaults 0 0
~]# cat /etc/fstab | awk -F"[ ]/?" '/^UUID/{print $2}'
boot
data
swap
或者
~]# cat /etc/fstab | awk -F"[ ]/|[ ]" '/^UUID/{print $2}'
boot
data
swap
5.面试题:将文件f3中的第一个点前的字符串取出再写进去
~]# cat f3
1 test.sina.com.cn
2 music.sina.com.cn
3 sports.sina.com.cn
4.news.sina.com.cn
~]# cat f3 | awk -F"[ .]" '{print $2}' >> f3
~]# cat f3
1 test.sina.com.cn
2 music.sina.com.cn
3 sports.sina.com.cn
4.news.sina.com.cn
test
music
sports
news
5-1.扩展
1.前面-F"[ .]"既然是表达或的意思,那么是否可以这么写-F"[ ]|."???
答案:不可以!
原因:因为此处用的是正则表达式,在正则表达式中点(.)代表任意单个字符,会把空格后的字母当成分隔符!
所以是不可以的,那么如何写?
2.如果不是点而是%呢?%是可以的,因为在正则表达式中%就是代表%,但是$,或其他在正则表达式中有特殊含义的不可以作为分隔符,如果一定要做分隔符,需要反斜线转义
示例:此处用3个反斜线转义
~]# cat f2 | awk -F"[ ]|\\\." '{print $2}'
test
music
sports
news
3.如果文本中的第一个点是$呢?
示例:此处是4个反斜线进行转义
~]# cat f2
1 test$sina.com.cn
2 music$sina.com.cn
3 sports$sina.com.cn
4 news$sina.com.cn
~]# cat f2 | awk -F"[ ]|\\\\$" '{print $2}'
test
music
sports
news
~]# cat f2 | awk -F"[ $]" '{print $2}'
test
music
sports
news
所以当用一个空格和具有特殊含义的符号时,最好是写在中括号[]里的
3.awk中的格式化输出命令:printf
写在介绍printf前,先对其进行总结:
1.使用printf动作输出的文本不会换行,如果需要换行,可以在对应的格式替换符后加入"\n"进行转义
2.使用printf动作时,指定的格式和被格式化的文本之间,需要用"逗号"隔开。
3.使用printf动作时,格式中的格式替换符比喻与被格式化的文本一一对应
1.printf命令-类似于shell里的printf
printf命令可以把内容以灵活的格式进行显示,如左对齐,右对齐
格式化输出:printf "FORMAT", item1, item2, ...
(1) 必须指定FORMAT
(2) 不会自动换行,需要显式给出换行控制符,\n
(3) FORMAT中需要分别为后面每个item指定格式符
格式符:与item一一对应
%c:显示字符的ASCII码
%d, %i:显示十进制整数 -用的比较多
%e, %E:显示科学计数法数值
%f:显示为浮点数
%g, %G:以科学计数法或浮点形式显示数值
%s:显示字符串 -用的比较多
%u:无符号整数 -用的比较多
%%:显示%自身
修饰符
#[.#] 第一个数字控制显示的宽度;第二个#表示小数点后精度,%3.1f
+ 左对齐(默认右对齐) %-15s
* 显示数值的正负符号 %+d
printf示例:
1.设置对齐格式以及字符数
~]# awk -F: '{printf "%-20s %-5d\n",$1,$3}' /etc/passwd
root 0
bin 1
pulse 171
gdm 42
gnome-initial-setup 990
其中$1为字符串,所以设置左对齐为20s个字符,$3为数字所以设置左对齐为5d个字符,printf默认不换行,所以需要加一个换行符
2.打印一个完整的报表格式
~]# awk -F: 'BEGIN{print "username |uid\n--------"} {printf "%-10s |%-5d\n",$1,$3}END{print "-------------"}' /etc/passwd
username |uid
-----------------------
root |0
bin |1
daemon |2
memcached |987
ceshi |1009
quagga |92
httpd |80
-------------------------
awk生成报表格式大概就是这个样子,这就是为什么awk称为报表生成器的原因
3.自定义字段示例
~]# awk -F: '{printf "Username: %s,UID:%d\n",$1,$3}' /etc/passwd
Username: root,UID:0
Username: bin,UID:1
Username: daemon,UID:2
Username: adm,UID:3
3-1.扩展
~]# awk -F: '{printf "Username: %-15s,UID:%d\n",$1,$3}' /etc/passwd
Username: root ,UID:0
Username: bin ,UID:1
Username: daemon ,UID:2
4.awk中的操作符
a.算术操作符:
x+y, x-y, x*y, x/y, x^y, x%y
- x:转换为负数
+x:将字符串转换为数值
字符串操作符:没有符号的操作符,字符串连接
赋值操作符:
=, +=, -=, *=, /=, %=, ^=,++, --,
b.比较操作符:
==, !=, >, >=, <, <=
模式匹配符:
~:左边是否和右边匹配包含 !~:是否不匹配
c.逻辑操作符:与&&,或||,非!
d.条件表达式(三目表达式)
selector?if-true-expression:if-false-expression
表达式;if-ture-xx:else-xxx
eg:x>y?var=x;var=y
5.AWK中的条件判断表达式,即三目表达式
相当于把shell中的if;then,else,fi的放到awk中,示例如下文
操作符用法示例:
1.下面两语句有何不同
~]# awk 'BEGIN{i=0;print ++i,i}' 结果 1 1
~]# awk 'BEGIN{i=0;print i++,i}' 结果 0 1
这说明实际上awk的语法是采用VC语言风格的
2.awk中~&!~是否包含的用法示例:
~]# awk -F: '$0 ~ /root/{print $1}' /etc/passwd
root
operator
意思是如果过滤的行中有包含root字符串的,则打印出这行的用户名
3.用到下文提到的patter模式,在这里是匹配是否包含root字符串
~]# awk -F: '$0 ~ "/root"{print $1}' /etc/passwd
root
operator
区别上面的这个写法,在这里是包含/root字符串的行
~]# awk -F: '$0 !~ /root/{print $1}' /etc/passwd
bin
daemon
adm
和上面的命令刚好相反,如果行不包含字符串root,则打印该行用户名
~]# awk -F: '$3==0' /etc/passwd
root:x:0:0:root:/root:/bin/bash
意思:判断UID是否等于0,是则打印该行,判断是否为管理员
~]# awk '$0~"^root"' /etc/passwd
root:x:0:0:root:/root:/bin/bash
意思:判断该行是不是以root开头的行,是则打印
4..awk中的与&&,或|| 非!的使用示例:
~]# awk –F: '$3>=0 && $3<=1000 {print $1}' /etc/passwd
如果0<=UID<=1000,则打印出该用户
~]# awk -F: '$3==0 || $3>=1000 {print $1,$3}' /etc/passwd
打印出UID等于0和UID>=1000的用户名和他的UID
~]# awk -F: '!($3==0) {print $1}' /etc/passwd -要加括号
打印出UID不等于0的用户名
~]# awk -F: '!($3>=500) {print $3}' /etc/passwd
如果UID<=500,时,打印出该用户的UID
5.AWK中的条件判断表达式示例
相当于把shell中的if;then,else,fi的放到awk中
~]# awk -F: '{$3>=1000?name="common":name="sys";print name,$1,$3}' /etc/passwd
sys root 0
sys bin 1
sys tcpdump 72
common test 1000
common nginx 1008
意思是判断用户是否为系统用户,是则打印并在开头加common,不是也打印在开头加sys
5.awk中的PATTERN和action
模式匹配和处理动作=sed的地址定界+修饰符
功能:和sed中的pattern一样起到过滤的功能,=sed的地址定界
模式:指定一个行的范围。该语法不能包括BEGIN和END模式。
BEGIN:让用户指定在第一条输入记录被处理之前所发生的动作,通常可在这里设置全局变量。
END:让用户在最后一条输入记录被读取之后发生的动作。
1.PATTERN:根据pattern条件,过滤匹配的行,再做处理
(1)如果未指定:空模式,匹配每一行
(2) /regular expression/:仅处理能够模式匹配到的行,需要用/ /括起来
~]# awk '/^UUID/{print $1}' /etc/fstab
~]# awk '!/^UUID/{print $1}' /etc/fstab
awk的匹配模式支持的是扩展的正则表达式
注意:不支持直接给出数字格式,但是可以变向的打印输出,详见下面的示例
(3) relational expression: 关系表达式,结果为“真”才会被处理
真:结果为非0值,非空字符串
假:结果为空字符串或0值都是假
字符串为空或者0为假
(4) line ranges:行范围
startline,endline:/pat1/,/pat2/ 不支持直接给出数字格式
awk -F: '/^root\>/,/^nobody\>/{print $1}' /etc/passwd
awk -F: '(NR>=10&&NR<=20){print NR,$1}' /etc/passwd
#NR表示行
(5) BEGIN/END模式
BEGIN{}: 仅在开始处理文件中的文本之前执行一次
END{}:仅在文本处理完成之后执行一次
patter用法示例:
1.先写一个特殊的用法
1.当在awk命令中使用正则模式时,使用到的正则用法属于"扩展的正则表达式"
2.当使用{x,y}这种次数匹配正则表达式,需要配合--posix或者--re-interval选项
备注:这是网上找到一个示例,文中提到{x,y}必须加这个选项,然而不加选项也是可以过滤的,awk是是支持posix字符集的
~]# cat f3
seex
sex
seeex
seeeeex
~]# cat f3 | awk '/se{2,3}x/{print $0}'
seex
seeex
~]# cat f3 | awk -posix '/se{2,3}x/{print $0}'
seex
seeex
~]# cat f3 | awk --re-interval '/se{2,3}x/{print $0}'
seex
seeex
~]# cat f3|grep -E "se{2,3}x"
seex
seeex
~]# cat f3|sed -nr '/se{2,3}x/p'
seex
seeex
2.取系统磁盘分区空间利用率df,/dev/sd开头的分区(上文中虽然取出来了,但是没过滤)
~]# df | awk -F"[[:space:]]+|%" '/^\/dev\/sd/{print $1,$5}'
/dev/sda2 8
/dev/sda3 1
/dev/sda1 17
3.取当前连接主机的IP地址(上文中虽然取出来了,但是没过滤)
~]# ss -nt| awk -F"[[:space:]]+|:" '/^ESTAB/{print $6}'
192.168.34.1
192.168.34.105
或者用NF的表达方式
~]# ss -nt| awk -F"[[:space:]]+|:" '/^ESTAB/{print $(NF-2)}'
192.168.34.1
192.168.34.105
4.取登录当前系统失败(lastb)用户的IP
~]# #lastb
root ssh:notty 192.168.34.105 Sun Nov 11 17:25 - 17:25 (00:00)
root23 ssh:notty 192.168.34.1 Mon Nov 5 15:43 - 15:43 (00:00)
btmp begins Fri Nov 2 09:58:52 2018
~]# lastb|awk '$3 ~ /[[:digit:]]/{print $3}'|sort|uniq -c
3 192.168.34.1
1 192.168.34.101
因为最后一行有btmp这样的字样,只能通过包含的功能来过滤掉最后一行
4-1.如果要取失败连接次数大于3的扔到防火墙,可以先取出来
~]# lastb|awk '$3 ~ /[[:digit:]]/{print $3}'|sort|uniq -c| awk '$1 >=3{print $2}'
192.168.34.1
5.patter中为关系表达式的示例(空字符串或0值都是假,其他为真)
~]# awk '0{print $0}' /etc/passwd -0为假,结果为空
~]# awk '""{print $0}' /etc/passwd -空也为假,结果为空
~]# awk '1{print $0}' /etc/passwd -1为真
~]# awk '" "{print $0}' /etc/passwd -空白符也为真
~]# awk -v abc=" " 'abc{print $0}' /etc/passwd -abc为空白,不是空字符串,也为真
~]# awk -v abc=" " '! abc{print $0}' /etc/passwd
#-abc为空白,为真,对真取反,结果为假
6.awk中patter的地址定界
~]# awk -F: '/^root/,/^adm/{print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
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
打印以root开头的行到以adm开头的行之间的所有行,相当于sed命令:
~]# sed -nr '/^root/,/^adm/p' /etc/passwd
7.如何打印从多少行到多少行之间的行??
~]# awk -F: 'NR>=10 && NR<=12 {print NR,$0}' /etc/passwd
10 operator:x:11:0:operator:/root:/sbin/nologin
11 games:x:12:100:games:/usr/games:/sbin/nologin
12 ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
通过变量NR变向的打印出行
8.取出/etc/fstab配置文件中以UUID开头的行
~]# cat /etc/fstab | awk '/^UUID/{print $0}'
UUID=9612633f-e7f1-4b28-8813-403d209d7abc / xfs defaults 0 0
UUID=0eba9e52-43a4-4c64-89bd-3fb639f0a6c1 /boot xfs defaults 0 0
相当于grep命令:~]# grep "^UUID" /etc/fstab
相当于sed命令:~]# sed -n '/^UUID/p' /etc/fstab
9.示例
~]# awk '!0' /etc/passwd =awk '!0{print $0}' /etc/passwd 省略了{print $0}的写法,结果为真,即打印全部行
~]# awk -F: 'i=1;j=1{print i,j}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
1 1
bin:x:1:1:bin:/bin:/sbin/nologin
1 1
???
10.示例
~]# awk -F: '$NF ~ /bash$/{print $1,$NF}' /etc/passwd
root /bin/bash
test /bin/bash
判断用户shell是否为/bin/bash,是则打印用户名和shell类型,此处用变量NF来实现
效果等于 awk -F: '$NF=="/bin/bash"{print $1,$NF}' /etc/passwd
效果等于 awk -F: '$7 == "/bin/bash"{print $1,$NF}' /etc/passwd
效果等于 awk -F: '$0 ~ /bash$/{print $1,$NF}' /etc/passwd
11.打印奇数行和偶数行
~]# seq 6 | awk 'i=!i' 打印奇数行
1
3
5
原理:i初始值为空,为假,取反时,则打印第一行,此时i=1,i=1时,为真,取反为假,所以第二行不打印,然后i=0,依次类推所以只打印奇数行
~]# seq 6 | awk '!(i=!i)' 打印偶数行
2
4
6
效果等于 seq 10 |awk -v i=1 'i=!i'
原理:同上,只要先定义i=1,为真,第一行就不打印了或者用sed打印奇数行和偶数行(用sed步进的方式)
seq 6 | sed -n '1~2p' 打印奇数行
seq 6 | sed -n '2~2p' 打印偶数行
2.action
action除了上文中支持的算术、条件判断,表达式,还支持循环,输入语句,输出语句、组合语句等功能
(1) Expressions:算术,比较表达式等
(2) Control statements:if, while等
(3) Compound statements:组合语句
(4) input statements
(5) output statements:print等
7.重点:awk的控制语句(if,while,do,for,break,continue,数组,exit等控制语句)
7.1.awk中if-else条件控制语句的语法及用法
语法:
1.双分支if
if(condition){statement;…}(多条语句用;隔开)[else statement]
2.多分支if
if(condition1){statement1}else if(condition2){statement2}else{statement3}
使用场景:对awk取得的整行或某个字段做条件判断
if-else示例:
1.如判断考试分数,写法如下
~]# awk -v score==80 'BEGIN{if(score <60){print "no pass"} else if(score <=80){print "soso"}else {print good}}'
soso
2.示例
~]# awk -F: '{if($3>=1000)print $1,$3}' /etc/passwd
判断UID是否大于1000,是则打印用户名和UID
~]# awk -F: '{if($NF=="/bin/bash") print $1}' /etc/passwd
判断用户shell是否为/bin/bash,是则打印用户名
~]# awk '{if(NF>5) print $0}' /etc/fstab
判断域或列个数是否大于5,是则打印该行
~]# awk -F: '{if($3>=1000) {printf "Common user: %s\n",$1} else {printf "root or Sysuser: %s\n",$1}}' /etc/passwd
等于上文中的:
~]# awk -F: '{$3>=1000?name="common":name="sys";print name,$1,$3}' /etc/passwd
3.随机生成1000个数字,用awk取出最大值和最小值(可以用shell写法)
1.生成1000个数字
~]# for i in {1..1000};do if [ $i -eq 1 ];then echo -e "$RANDOM\c" >> f1.txt;else echo -e ",$RANDOM\c" >> f1.txt ;fi;done
2.用awk取出最大值和最小值
~]# awk -F',' '{i=2;max=$1,while(i<=NF){if($i > max){max=$i}else if($i < min){min=$i};i++}}END{print "max="max,"min="min}' f1.txt
验证用awk是否取出的值为正确的:
方法一:用tr验证
tr ',' '\n' < f1.txt | sort -nr | head -n1
tr ',' '\n' < f1.txt | sort -nr | tail -n1
方法二:用shell脚本验证
#!/bin/bash
for i in {1..1000};do
7.2.awk中的循环语句:while、do-while、for循环
awk中的循环意思说明:
awk中的循环不是对行的循环,因为awk本身就支持文本中行的循环;这里是指对每一行中的字段或域或列进行循环,分别对行中的每个字段进行循环处理
1.awk中while循环控制语句的语法及用法
语法:while(condition){statement;…}
条件“真”,进入循环;条件“假”,退出循环
使用场景:
对一行内的多个字段逐一类似处理时使用
对数组中的各元素逐一处理时使用
此时涉及到系统自带的一个函数length(函数在下面会有介绍)
示例:
1.统计每一行第一个字段的长度
~]# awk -F: '{print length($1),$1}' /etc/passwd
4 root
3 bin
6 daemon
3 adm
2.统计/etc/passwd第一行的每个字段的长度
~]# awk -F: 'NR==1{i=1;while(i<=NF){print $i,length($i);i++}}' /etc/passwd
root 4
x 1
0 1
0 1
root 4
/root 5
/bin/bash 9
3.统计grub2.cfg文件中linux16那行的每个字段的长度
~]# awk '/^[[:space:]]*linux16/{i=1;while(i<=NF){print $i,length($i);i++}}' /etc/grub2.cfg
linux16 7
/vmlinuz-3.10.0-862.el7.x86_64 30
root=UUID=9612633f-e7f1-4b28-8813-403d209d7abc 46
ro 2
crashkernel=auto 16
rhgb 4
quiet 5
LANG=en_US.UTF-8 16
linux16 7
/vmlinuz-0-rescue-d874c9913a8e4f4f8b615f29c9a0388e 50
root=UUID=9612633f-e7f1-4b28-8813-403d209d7abc 46
ro 2
crashkernel=auto 16
rhgb 4
quiet 5
4.统计grub2.cfg文件中linux16那行的字段长度大于10的字段-用while循环
~]# awk '/^[[:space:]]*linux16/{i=1;while(i<=NF){if(length($i)>=10) {print $i,length($i)};i++}}' /etc/grub2.cfg
/vmlinuz-3.10.0-862.el7.x86_64 30
root=UUID=a389f1d4-3307-4f76-b4fd-2f48186a9640 46
crashkernel=auto 16
LANG=en_US.UTF-8 16
/vmlinuz-0-rescue-dcc1fbe5149645af898488eec902ae8f 50
root=UUID=a389f1d4-3307-4f76-b4fd-2f48186a9640 46
crashkernel=auto 16
5.面试题:用awk取出文本f5的最大值和最小值,需要先生成1000个随机数存到f5中
1.不用awk,可以通过脚本实现最大值和最小值
2.用awk如何来做??
shell脚本先生成1000个随机数:
~]# for i in `seq 1000`;do if [ $i -eq 1 ];then echo -e "$RANDOM\c" >> f5; else echo -e ",$RANDOM\c" >> f5 ;fi;done
awk取最大值最小值:
~]# awk -F"," '{i=2;max=$1;min=$1;while(i<=NF){if($i > max){max=$i} else if($i < min){min=$i};i++}}END{print "max="max,"min="min}' < f5
max=32711 min=39
2.awk中do-while循环控制语句
语法:do {statement;…}while(condition)
意义:无论真假,至少执行一次循环体
示例:求1-100正整数的和
~]# awk 'BEGIN{ total=0;i=0;do{ total+=i;i++;}while(i<=100);print total}'
5050
3.awk中for循环控制语句
语法:for(expr1;expr2;expr3) {statement;…}
常见用法:
for(variable assignment;condition;iteration process)
{for-body}
特殊用法:能够遍历数组中的元素
语法:for(var in array) {for-body}
for循环使用示例:
1.求1-100正整数的和:
~]# awk 'BEGIN{for(i=1;i<=100;i++)sum+=i;print sum}'
5050
2.统计grub2.cfg文件中linux16那行的字段长度大于10的字段-用for循环
~]# awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++){if(length($i)>=10){print $i,length($i)}}}' /etc/grub2.cfg
/vmlinuz-3.10.0-862.el7.x86_64 30
root=UUID=a389f1d4-3307-4f76-b4fd-2f48186a9640 46
crashkernel=auto 16
LANG=en_US.UTF-8 16
/vmlinuz-0-rescue-dcc1fbe5149645af898488eec902ae8f 50
root=UUID=a389f1d4-3307-4f76-b4fd-2f48186a9640 46
crashkernel=auto 16
7.3.awk中的switch控制语句和continue语句、break、next控制语句
1.switch控制语句
类似于shell中的case语句
语法:
switch(expression) {case VALUE1 or /REGEXP/: statement1; caseVALUE2 or /REGEXP2/: statement2; ...; default: statementn}
2.awk中的continue,break,next控制语句
break和continue
next:
提前结束对本行处理而直接进入下一行处理(awk自身循环)
1.continue的示例:
1.求1000以内偶数的和
~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i%2==0)continue;sum+=i}print sum}'
2500
2.求1000以内奇数的和
~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i%2==1)continue;sum+=i}print sum}'
2550
3.求1000以内除了66的所有数字的和
~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i==66)continue;sum+=i}print sum}'
4984
2.break的示例:
1.求1000数字中,当大于100时,跳出循环,即求100以内的和
~]# awk 'BEGIN{sum=0;for(i=1;i<=1000;i++){if(i>100)break;sum+=i}print sum}'
5050
3.next的示例:
因为提前结束对本行处理而直接进入下一行处理(因为awk本身就是循环行的功能)
1.打印/etc/passwd下的奇数行
~]# awk -F: '{if(NR%2==0)next;print NR,$0}' /etc/passwd
1 root:x:0:0:root:/root:/bin/bash
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
11 games:x:12:100:games:/usr/games:/sbin/nologin
13 nobody:x:99:99:Nobody:/:/sbin/nologin
还可以这样写:awk -F: 'NR%2==1{print NR,$0}' /etc/passwd
8.awk数组(重要:一个非常使用的功能)
awk中的数组说明:
awk的数组全都是关联数组
关联数组:array[index-expression]
其中index-expression:
(1) 可使用任意字符串;字符串要使用双引号括起来
(2) 如果某数组元素事先不存在,在引用时,awk会自动创建此元素,并将其值初始化为“空串”
(3) 若要判断数组中是否存在某元素,要使用“index in array”格式进行遍历
awk关联数组的遍历:
若要遍历数组中的每个元素,要使用for循环:因为关联数组下标是无序性
for(var in array) {for-body}
注意:var会遍历array的每个索引
数组的去重的效果示例:
~]# awk '!arr[$0]++' dupfile
~]# awk '{!arr[$0]++;print $0, arr[$0]}' dupfile echo abc abc ccc bcd ddd ddd abc abc >> f1.txt
1.数组的使用示例:
1.定义awk数组
~]# awk 'BEGIN{title["ceo"]="zhang";title["coo"]="liu";title["cto"]="wang";print title["ceo"]}'
zhang
可以用for循环把每一个数组的值都表示出来
~]# awk 'BEGIN{title["ceo"]="zhang";title["coo"]="liu";title["cto"]="wang";print title["ceo"];for(i in title){print i,title[i]}}'
zhang
coo liu
ceo zhang
cto wang
但输出时数组元素时,是无序的,这就是关联数组的特性
2.awk数组中的重要功能
~]# cat f6
abc
abc
ddd
ccc
aaa
ccc
ccc
~]# awk '!line[$0]++' f6
abc
ddd
ccc
aaa
问题:为什么执行结果是这个??
原因:
1.awk '!line[$0]++' f6 = awk '!line[$0]++{print $0}' f6 实际上是隐藏了{print $0},这个隐藏功能在awk很重要!但是要和后面patter中的空模式区别开
2.'!line[$0]++'= '!line[$0]++{print $0}'是省略了写法,是patter的关系表达式中,先判断再打印
3.而后面的数组里的是加了双括号{},{line[$0]++}即用的patter的空模式,即文本中的每一行都处理
这两个写法是不一样的,别混淆了
2-1.基于这个考虑进行分析 '!line[$0]++' = '!line[$0]++{print $0}'
1.当读取文本f6的第一行时=abc
!line["abc"]++,因为abc没有值,line["abc"]=0为假,取反!line["abc"]后为真;所以把第一行的abc打印了一次,打印第一行结束后line["abc"]=1
2.当读取文本f6的第二行时=abc
!line["abc"]++,在之前基础上line["abc"]=1为假,取反!line["abc"]=0后为假,所以把第二行的abc不打印,结束后line["abc"]=2
3.以此类推,发现后续出现abc都不会打印,但是line["abc"]都会在之前的基础上+1,即达到去重并统计的目的
而处理第三行!line["ddd"]++时,和上述abc一样,第一次出现则打印,后续也不会打印
所以,命令执行结果为去重的效果
从这个命令执行结果也可以明显看到上述的分析结果,可以看出abc的值是递增的,也就是abc出现的次数
~]# awk '{!line[$0]++;print $0, line[$0]}' f6
abc 1
abc 2
ddd 1
ccc 1
aaa 1
ccc 2
2.awk数组中的重要功能之for循环遍历数组,很具有实用性(在后面的统计服务的一些日志文件很有作用)
如果要理解遍历awk数组,需要深刻理解上述的示例2:awk '!line[$0]++' f6是怎么实现的,但还是有些和数组里的,遍历$2,把$2不同的值,当成数组的下标有些区别的
1.若要遍历数组中的每个元素,要使用for循环
for(var in array) {for-body}
注意:var会遍历array的每个索引
2.为什么要通过特殊写法去遍历awk中的数组?
如果是普通数组,用循环0,1,2,3做个循环即可;而awk的数组都是关联数组,[]中括号里的下标都不固定,所以要通过 for(var in array(数组名称)) {for-body}这样的特殊写法,var的值会从array数组元素取其下标,相当于每次循环var的值是等于array数组的下标的
注意:
当我们直接引用一个数组中不存在的元素时,awk会自动创建这个元素,并且为其赋值为"空字符串"
for循环遍历数组使用示例:
示例1:
~]# awk 'BEGIN{print test["ip"];test["ip"]++;print test["ip"]}'
1
可以看出执行结果中第一次输出为空,第二次自动加1
示例2:
~]# awk 'BEGIN{title["ceo"]="zhang";title["coo"]="liu";title["cto"]="wang";for(i in title){print title[i]}}'
liu
zhang
wang
结果分析:
for(i in title){print title[i],i的值来自于title的下标,即i=ceo coo cto,而且是无序打印
示例3:当前主机的连接状态及其个数
~]# netstat -tan | awk '/^tcp/{state[$NF]++}END{for(i in state) { print i,state[i]}}'
LISTEN 8
ESTABLISHED 3
结果分析:
state[$NF]++以空白做分隔符,统计同一类型的状态有多少个,for(i in state) { print i,state[i]} 即统计了出现状态的多少种以及出现的次数,不明白的可以看上文中的示例2:awk数组中的重要功能
当然,看懂这个命令,需要知道两个知识点:
1.空模式,即全文反复提到的{state[$NF]++}={state[$NF]++}{print $0}
2.END模式,END的模式是在前面动作结束之后,最后执行依次END模式中的命令,所以先不用考虑空模式
下面再一次对空模式中的处理过程,做详细的描述:
空模式中,我们创建了一个state数组,并将状态值(LISTEN、ESTABLISHED)作为引用下标,所以执行到第一行时,我们引用的是state["LISTEN"],很明显,这个元素并不存在,所以,当第一行被空模式的动作处理完毕后state["LISTEN"]的值已经被赋值为1了。这时,空模式中的动作继续处理下一行,而下一行的$NF=ESTABLISTEN;state["ESTABLISTEN"]所以,state["ESTABLISTEN"]第一次参与运算过程与上一个原理相同直到再次遇到相同的状态时,使用同样一个IP地址作为下标的元素将会再次被自加直到处理完所有的行,开始执行END模式中的动作。而END模式中,我们打印出state数组中的所有元素的下标,以及元素对应的值。此刻,state数组中的下标即为各种状态,元素的值即为对应状态出现的次数,最终,我们统计出每个状态出现的次数。
4.统计/var/log/httpd/access_log,每个IP链接的次数
~]# cat /var/log/httpd/access_log #文本内容如下
192.168.34.1 - - [15/Oct/2018:22:02:35 +0800] "GET / HTTP/1.1" 403 4897 "-" "Mozil"
192.168.34.1 - - [15/Oct/2018:22:02:35 +0800] "GET /noindex/css/bootstrap.min.css"
~]# awk '{ip[$1]++}END{for(i in ip){print i,ip[i]}}' /var/log/httpd/access_log
192.168.34.101 9
192.168.34.103 13
::1 4
192.168.34.1 88
效果等于:
~]# awk '{print $1}' /var/log/httpd/access_log |sort|uniq -c
4 ::1
88 192.168.34.1
9 192.168.34.101
13 192.168.34.103
5.统计ss -nt ip链接次数
~]# ss -nt|awk -F"[[:space:]]+|:" '/^ESTAB/{ip[$(NF-2)]++}END{for(i in ip){print i,ip[i]}}'
192.168.34.105 1
192.168.34.1 1
效果等于:
~]# ss -nt|awk -F"[[:space:]]+|:" '/^ESTAB/{print $(NF-2)}'|sort|uniq -c
1 192.168.34.1
1 192.168.34.105
6.统计/etc/fstab文件系统类型分别有多少个
~]# cat /etc/fstab | awk '/^UUID/{file[$3]++}END{for(i in file){print i,file[i]}}'
swap 1
xfs 3
7.求下表中的男生和女生的平均成绩
~]# cat f9
name sex score
a m 90
b f 80
c f 99
d m 88
e m 80
7-1.对示例7扩展:如何利用awk的数组功能来求?
思路:先求男的和和女的和?利用两个数组?
~]# cat f9|awk '!/^name/{sum[$2]+=$3}END{for(i in sum){print i,sum[i]}}' -求男女分的和
m 258
f 179
~]# cat f9|awk '!/^name/{sum[$2]+=$3;num[$2]++}END{for(i in sum){print i,sum[i]/num[i]}}'
m 86
f 89.5
8.统计下面每个名字出现的次数
~]# cat f1
Allen Phillips
Green Lee
William Aiden Janmes Lee
Angel Jack
Jack Thomas
Lucas Kevin
Tyler Lee
William Allen
~]# cat f1 | awk '{for(i=1;i<=NF;i++){count[$i]++}}END{for(j in count){print j,cuont[j]}}'
Tyler
Angel
Lucas
William
Thomas
Green
Jack
Phillips
Kevin
9.awk函数(分为内置函数和自定义函数)
1.内置函数包括:rand、length、sub、gsub、split、system
rand(i)数值处理:返回0和1之间一个随机数
示例:
awk 'BEGIN{srand(); for (i=1;i<=10;i++)print int(rand()*100) }'
length([s]):字符串处理,返回指定字符串的长度
sub(r,s,[t]):
对t字符串搜索r表示模式匹配的内容,并将第一个匹配内容替换为s
示例:
echo "2008:08:08 08:08:08" | awk 'sub(/:/,"-",$1)'
gsub(r,s,[t]):
对t字符串进行搜索r表示的模式匹配的内容,并全部替换为s所表示的内容
示例:
echo "2008:08:08 08:08:08" | awk 'gsub(/:/,"-",$0)'
split(s,array,[r]):
以r为分隔符,切割字符串s,并将切割后的结果保存至array所表示的数组中,第一个索引值为1,第二个索引值为2,…
示例:
netstat -tn | awk '/^tcp\>/{split($5,ip,":");count[ip[1]]++}'END{for (i in count) {print i,count[i]}
示例1:内置函数之sub、gsub、split实现搜索替换切割的用法
sub(r,s,[t]):对t字符串搜索r表示模式匹配的内容,并将第一个匹配内容替换为s
但sub(r,s,[t])不是贪婪模式,默认值替换搜索出来的第一个
gsub(r,s,[t])表示全局替换
~]# echo "2008:08:08 08:08:08" | awk 'sub(/:/,"-",$1)'
2008-08:08 08:08:08
只替换$1
~]# echo "2008:08:08 08:08:08" | awk 'gsub(/:/,"-",$1)'
2008-08-08 08:08:08
全局替换$0
~]# echo "2008:08:08 08:08:08" | awk 'gsub(/:/,"-",$0)'
2008-08-08 08-08-08
示例2:统计字符串中每个字母出现的次数abcdaabbccdd
~]# echo abcdaabbccdd | awk 'gsub(//,".",$0)'|awk -F"." '{for(i=2;i<=NF-1;i++)num[$i]++}END{for(j in num){print j,num[j]}}'
a 3
b 3
c 3
d 3
在文章最后会用另外一种写法进行过滤(文章最后会以以空位分隔符的特殊写法来表示)
示例3:split的切割功能
1.统计链接本机的IP和端口号
~]# netstat -tn | awk '/^tcp\>/{split($5,ip,":");count[ip[1]]++}END{for (i in count) {print i,count[i]}}'
192.168.34.105 1
192.168.34.1 1
~]# netstat -tn | awk '/^tcp\>/{split($5,ip,":");count[ip[2]]++}END{for (i in count) {print i,count[i]}}'
8816 1
49746 1
结果分析:
split($5,ip,":")将192.168.34.1:8816进行切割,数组ip的下标1放IP地址,下标2放端口号,count[ip[1]]++ 把ip下标为1也就是IP地址,把出现的IP个数,又给count当下标,就统计每个IP出现的次数,count[ip[2]]++ 把ip下标为2也就是端口号,把出现的端口号个数,又给count当下标,就统计每个端口号出现的次数
2.awk中的自定义函数格式:
awk自定义函数是用正规开发语言的函数格式:
function name ( parameter, parameter, ... ) {
statements
return expression
}
示例:awk自定义函数的用法:
~]# cat fun.awk,把函数写到文件中
function max(x,y) {
x>y?var=x:var=y
return var
}
~]# BEGIN{print max(i,j)} awk -v i=10 -v j=20 -f fun.awk
调用函数即可,和shell中的函数类似
3.awk中很实用的内置函数system命令
system函数作用:在awk可以反过来调用linux里的命令
场景:
空格是awk中的字符串连接符,如果system中需要使用awk中的变量可以使用空格分隔,或者说除了awk的变量外其他一律用""引用起来
示例1:显示/boot/grub2下的文件列表;调用命令时要加双引号
~]# awk 'BEGIN{system("ls /boot/grub2")}'
device.map fonts grub.cfg grubenv i386-pc locale
或者这么写,先定义变量等于路径,再调用变量
~]# awk 'BEGIN{dir="/boot/grub2";system("ls "dir)}'
device.map fonts grub.cfg grubenv i386-pc locale
调用hostname命令,显示主机名
~]# awk 'BEGIN{system("hostname")}'
centos7.localdomain
示例2:
之前处理过lastb中登录失败的IP,如果失败登录次数大于3次,扔到防火墙里,当时是先取出IP放到文件里,然后iptables再禁用;现在可以在awk中先取出IP,然后通过system("iptables " IP)就可以直接扔到防火墙中,具体实现?
10.awk脚本
awk脚本是指将awk程序写成脚本,直接调用或执行
awk脚本使用示例:
1.先写文本再调用
~]# cat f1.awk
{if($3>=1000)print $1,$3}
~]# awk -F: -f f1.awk /etc/passwd
2.也可以写成脚本形式,先写再调用
~]# vim f2.awk
#!/bin/awk -f
{if($3 >=1000)print $1,$3}
~]# ./f2.awk -F: /etc/passwd
nfsnobody 65534
test 1000
gentoo 1007
nginx 1008
ceshi 1009
向awk脚本传递参数
格式:
<a href=""></a>wkfile var=value var2=value2... Inputfile
注意:
在BEGIN过程中不可用。直到首行输入完成以后,变量才可用。可以通过-v参数,让awk在执行BEGIN之前得到变量的值。命令行中每一个指定的变量都需要一个-v参数
awk脚本传参使用示例:
~]# cat test.awk
#!/bin/awk –f
{if($3 >=min && $3<=max)print $1,$3}
~]# chmod +x test.awk
test.awk -F: min=100 max=200 /etc/passwd
11.工作中遇到的常用awk文本解决案例:
1.Linux Web服务器网站故障分析常用的命令(系统连接状态篇)
1.查看TCP连接状态
~]# netstat -nat |awk '{print $6}'|sort|uniq -c|sort -rn
每出现一被/^tcp/模式匹配到的行,数组S[$NF]就加1,NF为当前匹配到的行的最后一个字段,此处用其值做为数组S的元素索引;
~]# netstat -n | awk '/^tcp/ {++S[$NF]};END {for(a in S) print a, S[a]}'
或
~]# netstat -n | awk '/^tcp/ {++state[$NF]}; END {for(key in state) print key,"\t",state[key]}'
或
~]# netstat -n | awk '/^tcp/ {++arr[$NF]};END {for(k in arr) print k,"t",arr[k]}'
或者
~]# netstat -n |awk '/^tcp/ {print $NF}'|sort|uniq -c|sort -rn
或者
~]# netstat -ant | awk '{print $NF}' | grep -v '[a-z]' | sort | uniq -c
2.查找请求数请20个IP(常用于查找攻来源):
方法一:
~]# netstat -anlp|grep 80|grep tcp|awk '{print $5}'|awk -F: '{print $1}'|sort|uniq -c|sort -nr|head -n20
方法二:
~]# netstat -ant |awk '/:80/{split($5,ip,":");++A[ip[1]]}END{for(i in A) print A[i],i}' |sort -rn|head -n20
3.用tcpdump嗅探80端口的访问看看谁最高
~]# tcpdump -i eth0 -tnn dst port 80 -c 1000 | awk -F"." ‘{print $1"."$2"."$3"."$4}’ | sort | uniq -c | sort -nr |head -20
4.查找较多time_wait连接
~]# netstat -n|grep TIME_WAIT|awk ‘{print $5}’|sort|uniq -c|sort -rn|head -n20
5.找查较多的SYN连接
~]# netstat -an | grep SYN | awk ‘{print $5}’ | awk -F: ‘{print $1}’ | sort | uniq -c | sort -nr | more
6.根据端口列进程
~]# netstat -ntlp | grep 80 | awk ‘{print $7}’ | cut -d/ -f1
2.网站日志分析篇1(Apache):
1.获得访问前10位的ip地址
~]# cat access.log|awk ‘{print $1}’|sort|uniq -c|sort -nr|head -10
~]# cat access.log|awk ‘{counts[$(11)]+=1}; END {for(url in counts) print counts[url], url}’
2.访问次数最多的文件或页面,取前20
~]# cat access.log|awk ‘{print $11}’|sort|uniq -c|sort -nr|head -20
3.列出传输最大的几个exe文件(分析下载站的时候常用)
~]# cat access.log |awk ‘($7~/.exe/){print $10 " " $1 " " $4 " " $7}’|sort -nr|head -20
4.列出输出大于200000byte(约200kb)的exe文件以及对应文件发生次数
~]# cat access.log |awk ‘($10 > 200000 && $7~/.exe/){print $7}’|sort -n|uniq -c|sort -nr|head -100
5.如果日志最后一列记录的是页面文件传输时间,则有列出到客户端最耗时的页面
~]# cat access.log |awk ‘($7~/.php/){print $NF " " $1 " " $4 " " $7}’|sort -nr|head -100
6.列出最最耗时的页面(超过60秒的)的以及对应页面发生次数
~]# cat access.log |awk ‘($NF > 60 && $7~/.php/){print $7}’|sort -n|uniq -c|sort -nr|head -100
7.列出传输时间超过 30 秒的文件
~]# cat access.log |awk ‘($NF > 30){print $7}’|sort -n|uniq -c|sort -nr|head -20
8.统计网站流量(G)
~]# cat access.log |awk ‘{sum+=$10} END {print sum/1024/1024/1024}’
9.统计404的连接
~]# awk ‘($9 ~/404/)’ access.log | awk ‘{print $9,$7}’ | sort
10. 统计http status
~]# cat access.log |awk '{counts[$(9)]+=1}; END {for(code in counts) print code, counts[code]}'
~]# cat access.log |awk '{print $9}'|sort|uniq -c|sort -rn
10.蜘蛛分析,查看是哪些蜘蛛在抓取内容。
~]# /usr/sbin/tcpdump -i eth0 -l -s 0 -w - dst port 80 | strings | grep -i user-agent | grep -i -E 'bot|crawler|slurp|spider'
3.网站日分析2(Squid篇)按域统计流量
~]# cat squid_access.log.tar.gz| awk '{print $10,$7}' |awk 'BEGIN{FS="[ /]"}{trfc[$4]+=$1}END{for(domain in trfc){printf "%st%dn",domain,trfc[domain]}}'
4.安全篇:(ssh lastb)
ssh日志中失败登录的IP,取出来/var/log/secure
~]# awk '/Failed PASSWORD/{IP[$(NF-3)]++}END{for(i in IP){print i,IP[i]}' /var/log/secure
其他收集案例:
1.文件ip_list.txt如下格式,请提取”.cre.com”前面的主机名部分并写入到回到该文件中
1 blog.cre.com
2 www.cre.com
…
999 study.cre.com
2.统计/etc/fstab文件中每个文件系统类型出现的次数
~]# cat /etc/fstab | awk '/^UUID/{print $3}'|sort|uniq -c |sort -nr
3 xfs
1 swap
或者
~]# cat /etc/fstab | awk '/^UUID/{file[$3]++}END{for(i in file){print i,file[i]}}'
swap 1
xfs 3
3.统计/etc/fstab文件中每个单词出现的次数
~]# cat /etc/fstab |awk '{for(i=1;i<=NF;i++){count[$i]++}}END{for(j in count){print j,count[j]}}'
man 1
4.提取出字符串Yd$C@M05MB%9&Bdh7dq+YVixp3vpw中的所有数字
~]# echo "Yd$C@M05MB%9&Bdh7dq+YVixp3vpw"|awk 'gsub(/[^0-9]/,"",$0)'
05973
或者
~]# echo "Yd$C@M05MB%9&Bdh7dq+YVixp3vpw"|tr -dc "[0-9]"
05973
5.有一文件记录了1-100000之间随机的整数共5000个,存储的格式100,50,35,89…请取出其中最大和最小的整数
6.解决DOS攻击生产案例:根据web日志或者或者网络连接数,监控当某个IP并发连接数或者短时内PV达到100,即调用防火墙命令封掉对应的IP,监控频率每隔5分钟。防火墙命令为:iptables -A INPUT -s IP -j REJECT
~]# awk '{access[$1]++}END{for(i in access){if(access[i]>=1){print i,access[i]}}}' /var/log/httpd/access_log
192.168.34.101 9
192.168.34.103 13
只过滤出IP,监控任务可以写到计划任务里,或者用内置函数system["iptables"]调用?
7.将以下文件内容中FQDN取出并根据其进行计数从高到低排序
http://mail.magedu.com/index.html
http://www.magedu.com/test.html
http://study.magedu.com/index.html
http://blog.magedu.com/index.html
http://www.magedu.com/images/logo.jpg
http://blog.magedu.com/20080102.html
~]# cat f5|sed -nr 's@.*//([^/]+)/.*@\1@p'|sort|uniq -c|sort -nr
2 www.magedu.com
2 blog.magedu.com
1 study.magedu.com
1 mail.magedu.com
~]# cat f5|awk -F"/" '{print $3}'|sort|uniq -c|sort -nr
2 www.magedu.com
2 blog.magedu.com
1 study.magedu.com
1 mail.magedu.com
8.将以下文本以inode为标记,对inode相同的counts进行累加,并且统计出同一inode中,beginnumber的最小值和endnumber的最大值
inode|beginnumber|endnumber|counts|
106|3363120000|3363129999|10000|
106|3368560000|3368579999|20000|
310|3337000000|3337000100|101|
310|3342950000|3342959999|10000|
310|3362120960|3362120961|2|
311|3313460102|3313469999|9898|
311|3313470000|3313499999|30000|
311|3362120962|3362120963|2|
输出的结果格式为:
310|3337000000|3362120961|10103|
311|3313460102|3362120963|39900|
106|3363120000|3368579999|30000|
~]# awk -F'|' 'NR==1{print;next}{a[$1]?(a[$1]>$2?a[$1]=$2:0):(a[$1]=$2);b[$1]?(b[$1]<$3?b[$1]=$3:0):(b[$1]=$3);c[$1]+=$4}END{l=asorti(a,d);for(i=1;i<=l;i++)print d[i] FS a[d[i]] FS b[d[i]] FS c[d[i]] FS}' file
结果分析:
第一行直接打印。从第2行开始以$1为下标,建立3个数组,比较出$2的最小值,$3的最大值,然后把$4进行累加,最后进行排序后依次取出各项值。这其中运用了三目运算的嵌套,跟我们 if(){if(){}}else{}的使用是一个道理,不要认为复杂,如果觉得模糊不清,仔细读懂流程控制。
9.统计字符串中每个字母出现的次数abcdaabbccdd
~]# echo abcdaabbccdd | awk 'gsub(//,".",$0)'|awk -F"." '{for(i=2;i<=NF-1;i++)num[$i]++}END{for(j in num){print j,num[j]}}'
a 3
b 3
c 3
d 3
下面的写法在双引号前一定要加一个空格才能匹配出来或者用单引号,但是也需要在前面几个空格
~]# echo abcdaabbccdd|awk -F "" '{for(i=1;i<=NF;i++)x[$i]++}END{for(i in x){print i,x[i]}}'
a 3 此处一定有个空格
b 3
c 3
d 3
或者用单引号,但是也需要在前面几个空格
~]# echo abcdaabbccdd |awk -F '' '{for(i=2;i<=NF-1;i++)num[$i]++}END{for(j in num){print j,num[j]}}'
a 2
b 3
c 3
d 2
~]# echo abcdaabbccdd | grep -o "[a-z]"|sort|uniq -c
3 a
3 b
3 c
3 d
10.面试题:取出/etc/fstab中的挂载目录
~]# cat /etc/fstab | awk -F "[ ]/?" '/^UUID/{print $2}'
boot
data
swap
~]# cat /etc/fstab | awk -F "[ ]/|[ ]" '/^UUID/{print $2}'
boot
data
swap
12.awk -F 使用多个字符作为分隔符的意义和示例输入分隔符
说明:
1.我们可以使用两次awk -F命令,每次分别指定一个分隔符来进行操作,但是这样太麻烦,还有更简单的方法,即一次指定多个分隔符。
2.要一次指定多个分隔符,需要将分隔符用中括号[]包裹起来,如果多个分隔符中有至少一个特殊字符,那么还需要在中括号外加上双引号或者单引号,并且使用两个或两个以上的\将其进行转义
$、^、(、)、[、]、?、.、|
1:
~]# cat b.txt
ssh:user1@192.168.1.10
ssh:user2@192.168.1.11
ssh:user3@192.168.1.12
1.取user和IP
~]# awk -F [:@] '{print $2,$3}' b.txt
user1 192.168.1.10
user2 192.168.1.11
user3 192.168.1.12
2.上面b.txt中的:和@换成^和|,又该怎么取?
~]# awk -F '[\\^\\|]' '{print $2,$3}' b.txt
user1 192.168.1.10
user2 192.168.1.11
user3 192.168.1.12
2:
george[walker]bush
william[jefferson]clinton
如果要打印出由分隔符[和]分隔的三段数据,即可以分别使用两个或两个以上的\对[和]进行转义,如下所示
方法一:
~]# awk -F '[\\[\\]]' '{ print $1,$2,$3}' name.txt
george walker bush
william jefferson clinton
方法二:
~]# awk -F '[][]' '{ print $1,$2,$3}' name.txt
george walker bush
william jefferson clinton
3.取出文本中的姓名和FQDN,awk的方法参照文章最上面的链接文章
~]# cat a.txt
xiaoming\t20\thttp://sougou.com
xiaohua\t25\thttp://www.baidu.com
xiaodong\t30\thttp://www.jidong.com
方法一:用awk取,以\t做分隔符,但是在表示方法上比较特殊
~]# awk -F'\\\\t' '{print $1,$3}' a.txt
xiaoming http://sougou.com
xiaohua http://www.baidu.com
xiaodong http://www.jidong.com
分析:
第一个给第二个转义,第三个给第四个转义,传给awk对就是\\t,awk再将\\解释成\t
方法二:用awk和sed取
~]# awk -F'\\' '{print $1,$3}' a.txt |sed -r 's@(.* )t(.*)@\1\2@'
xiaoming http://sougou.com
xiaohua http://www.baidu.com
xiaodong http://www.jidong.com
14.扩展3题的内容把t换成$,又该如何取?
~]# cat a.txt
xiaoming\$20\$http://sougou.com
xiaohua\$25\$http://www.baidu.com
xiaodong\$30\$http://www.jidong.com
方法一:还是只用awk来取
~]# awk -F'\\\\\\$' '{print $1,$3}' a.txt
xiaoming http://sougou.com
xiaohua http://www.baidu.com
xiaodong http://www.jidong.com
分析:
1.\\$是转义$的
2.前四个\\\\是转义\的
方法二:awk和sed取
~]# awk -F'\\' '{print $1,$3}' a.txt |sed -r 's@(.* )\$(.*)@\1\2@'
xiaoming http://sougou.com
xiaohua http://www.baidu.com
xiaodong http://www.jidong.com
分析:先用awk用\做分隔符来取,再用sed取,sed中的$需要转义