文本处理三剑客之awk

文本处理三剑客之Awk-文章涵盖awk的诸多用法讲解

文本处理三剑客之awk_第1张图片

Awk的用户使用指南awk用户指南

相关链接文章:
正则表达式: 正则表达式
grep文本编辑: grep用法
sed文本编辑: sed用法

总结对比一下这三个剑客的特长之处

grep、sed、awk被称为linux中的三剑客

grep更适合单纯的查找或匹配文件.
sed更适合编辑皮匹配到的文本
awk更适合格式化文本,对文本进行比较复杂格式处理

文本三剑客都是默认逐行处理,自带循环
sed 可以对文本进行修改,而grep&awk 只是对文本进行过滤,不进行内容修改

awk中的-F后面的分隔符,要用单引号'',双引号""会产生弱引用,比如特殊符号'\\' '\$'
关于awk中的单引号和双引号的问题参照:awk中的输入分隔符单引号&双引号
学习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}

学习中遇到的混淆的问题:

'!line[$0]++'= '!line[$0]++{print $0}'是省略了写法,是patter的关系表达式中,先判断再打印
而后面的数组里的是加了双括号{},即用的patter的空模式,即文本中的每一行都处理


Awk

基本用法和功能以及各个功能示例:
awk介绍
awk基本用法
awk变量
awk格式化-printf
awk操作符
awk条件判断
awk循环
awk数组
awk函数
调用系统命令



awk介绍:

whatis awk?
awk:Aho, Weinberger, Kernighan,报告生成器,格式化文本输出,输出成报表格式
linux上默认使用 GNU awk(gawk)

[root@centos7 data]which awk
/usr/bin/awk
[root@centos7 data]ll /usr/bin/awk 
lrwxrwxrwx. 1 root root 4 Sep 19 11:45 /usr/bin/awk -> gawk

which awk=/usr/bin/awk 是gawk的软链接 


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

用法解析及示例:

$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的内容作为表尾

BEGIN&END

BEGIN:让用户指定在第一条输入记录被处理之前所发生的动作,通常可在这里设置全局变量。
END:让用户在最后一条输入记录被读取之后发生的动作。

分隔符:

awk默认使用空白符作为字段或列的分隔符;多个空白符被认为是一个,空白符包括空格,tab键/t,回车\n
也可以自定义-F"分隔符"自定义分隔符

print&printf的区别:

print命令只是单纯的把特定的内容进行打印,默认换行
printf命令可以把内容以灵活的格式进行显示,如左对齐,右对齐

示例

1.awk支持标准输入输出,后面可以不跟文件
[root@centos7 ~]#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 


Awk中的变量:

变量分为:内置变量和自定义变量

awk中的内置变量除了$0,$1,$2等,还有以下几种;
如果要使用这些变量需要加-v 选项先进行定义

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

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变量做输出分隔符

RS:输入记录分隔符,指定输入时的换行符,自定义行的分隔符 =record记录
默认一行叫记录,行是以\n回车作为切割符的,RS可以自定义不用回车作为分隔符
    awk -v RS=' ' ‘{print }’ /etc/passwd
    awk -v RS=':' '{print NR,$0}'/etc/passwd 
    [root@centos7 ~]cat f1
    aa;xxx:bb;bzzzz:cc
    dd:eex;zccc:xxxx
    [root@centos7 ~]cat f1| awk -v RS=":" '{print $0}'
    aa;xxx
    bb;bzzzz
    cc
    dd
    eex;zccc
    xxxx
    以RS=:冒号自定义行的分隔符,输出结果如上
    [root@centos7 ~]#cat f1| awk -v FS=";" -v RS=":" '{print $1}'
    aa
    bb
    cc
    dd
    eex
    xxxx
    自定义FS&RS,输出结果如上

ORS:输出记录分隔符,输出时用指定符号代替换行符 
awk -v RS=' ' -v ORS='###'‘{print }’ /etc/passwd
[root@centos7 ~]cat f1
aa;xxx:bb;bzzzz:cc
dd:eex;zccc:xxxx
[root@centos7 ~]cat f1| awk -v FS=";" -v RS=":" -v ORS"===" '{print $1}'
aa==bb==cc
dd==eex==xxxx
==
自定义FS,RS,ORS结果很明显

接下来是一个比较重要的变量
NF:字段数量,也就是域或列的总数量
awk -F: '{print NF}' /etc/passwd 以冒号做分隔符,显示每行的列的总数量
awk -F: '{print $NF}' /etc/passwd 显示以冒号做分隔符,每一行的最后一个字段即bash类型
awk -F:'{print $(NF-1)}' /etc/passwd 显示以冒号做分隔符,每一行的倒数第二个字段
统计光盘中所有安装包适用的cpu架构类型
root@centos7 mnt]#ls /mnt/Packages/*.rpm | awk -F"." '{print $(NF-1)}'|sort|uniq -c
   1371 noarch
   2600 x86_64

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是一行的
[root@centos7 ~]cat f1
aa;xxx:bb;bzzzz:cc
dd:eex;zccc:xxxx
[root@centos7 ~]cat f1| awk  -v RS=":" '{print NR$0}'
1aa;xxx
2bb;bzzzz
3cc
dd
4eex;zccc
5xxxx

2.备注:如果awk跟两个文件,awk会把两文件合并成一个文件,统计总行数,都取第一个字段的信息
如果需要分开显示统计,则用FNR
[root@centos7 ~]awk -F: '{print NR,$1}' /etc/passwd /etc/group
1 root
2 bin
3 daemon
4 adm

FNR:各文件分别计数,记录号

1.FNR:多个文件,每个分别统计显示第一个字段并列出来
awk '{print FNR}' /etc/fstab /etc/inittab
[root@centos7 ~]awk -F: '{print FNR,$1}' /etc/passwd /etc/group
1 root
2 bin
3 daemon
48 quagga
49 httpd
1 root
2 bin

FILENAME:当前文件名

1.统计时,加上变量可以显示文件名
awk '{print FILENAME}' /etc/fstab
[root@centos7 ~]awk -F: '{print FILENAME,FNR,$1}' /etc/passwd 
/etc/passwd 1 root
/etc/passwd 2 bin
/etc/passwd 3 daemon

ARGC:命令行参数的个数
awk '{print ARGC}' /etc/fstab /etc/inittab 结果为3
awk 'BEGIN {print ARGC}' /etc/fstab /etc/inittab 结果为3
ARGC统计参数:awk单独算一个参数,后面的每个文件算一个参数,通过下面的数组可以体现出来

ARGV:数组,保存的是命令行所给定的各参数

1.显示awk的每个参数分别是哪个
[root@centos7 ~]awk '{print ARGV[0]}' /etc/fstab /etc/inittab
awk
[root@centos7 ~]awk '{print ARGV[1]}' /etc/fstab /etc/inittab
/etc/fstab

示例:

1.统计当前网络连接情况的ip地址是 ss -nt
ss -nt | awk '{print $5}'

2.取/var/log/httpd/access_log的时间如下:
root@centos7 ~]cat /var/log/httpd/access_log
192.168.34.1 - - [15/Oct/2018:22:02:35 +0800] "GET / HTTP/1.1" 403 4897 "-" 
分两步取:
cat /var/log/httpd/access_log | awk '{print $4}'|awk -F"[" '{print $2}'
一步取:
cat /var/log/httpd/access_log | awk -F "[\[ ]" '{print $5}'
原理分析:[\[ ]代表(转义)\[和空格都算是分隔符,正则表达式的写法,或的关系,
而以空格分隔符,时间段为$4,那为什么是$5?在空格的标准里,[算$4,所以时间段为$5

3.取出磁盘分区利用率 -这次只取出利用率
两步取出:
df | awk -F% '{print $1}'|awk '{print $5}'
一步取出:
df | awk -F"[[:space:]]+|%" '{print $5}' awk用的是扩展的正则表达式


面试题:3-1,取出fstab中挂载的目录
[root@centos7 ~]cat /etc/fstab 
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
#
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
[root@centos7 ~]cat /etc/fstab | awk -F"[ ]/?" '/^UUID/{print $2}'
boot
data
swap
或者
[root@centos7 ~]cat /etc/fstab | awk -F"[ ]/|[ ]" '/^UUID/{print $2}'
boot
data
swap


4.面试题:将文件f3中的第一个点前的字符串取出再写进去
[root@centos7 ~]cat f3
1 test.sina.com.cn
2 music.sina.com.cn
3 sports.sina.com.cn
4.news.sina.com.cn
[root@centos7 ~]cat f3 | awk -F"[ .]" '{print $2}' >> f3
[root@centos7 ~]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

4-1,扩展
前面-F"[ .]"既然是表达或的意思,那么是否可以这么写-F"[ ]|."???
答案:不可以!
原因:因为此处用的是正则表达式,在正则表达式中点(.)代表任意单个字符,会把空格后的字母当成分隔符!
所以是不可以的,那么如何写?

如果不是点而是%呢?%是可以的,因为在正则表达式中%就是代表%,
但是$,或其他在正则表达式中有特殊含义的不可以作为分隔符,如果一定要做分隔符,需要反斜线转义
如下:
此处用3个反斜线转义
[root@centos7 ~]cat f2 | awk -F"[ ]|\\\." '{print $2}'
test
music
sports
news

那如果文本中的第一个点是$呢?
此处是4个反斜线进行转义
[root@centos7 ~]cat f2
1 test$sina.com.cn
2 music$sina.com.cn
3 sports$sina.com.cn
4 news$sina.com.cn
[root@centos7 ~]cat f2 | awk -F"[ ]|\\\\$" '{print $2}'
test
music
sports
news
[root@centos7 ~]cat f2 | awk -F"[ $]" '{print $2}'
test
music
sports
news
当用一个空格和具有特殊含义的符号时,最好是写在中括号[]里的

AWK中自定义变量

自定义变量(区分字符大小写)
(1) -v var=value
(2) 在program中直接定义
(2-1)program可以放到一个文本里,awk -f 直接调用即可
示例:
自定义变量可以-v var定义,也可以放到program即{}里,变量要先定义,后使用
awk -F:  '{name="magedu";print $1,name}' /etc/passwd
awk -F: -v name="magedu" '{print $1,name}' /etc/passwd
例如
cat awk.txt
{print $1,$2,$6}
awk -F: -f awk.txt /etc/passwd =awk -F: '{print $1,$2,$6}' /etc/passwd


Awk中的格式化

在介绍printf前,先对其进行总结:
1.使用printf动作输出的文本不会换行,如果需要换行,可以在对应的格式替换符后加入"\n"进行转义
2.使用printf动作时,指定的格式和被格式化的文本之间,需要用"逗号"隔开。
3.使用printf动作时,格式中的格式替换符比喻与被格式化的文本一一对应

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.设置对齐格式以及字符数
[root@centos7 ~]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.打印一个完整的报表格式
root@centos7 ~]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.
[root@centos7 ~]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

4.
[root@centos7 ~]awk -F: '{printf "Username: %-15s,UID:%d\n",$1,$3}' /etc/passwd
Username: root           ,UID:0
Username: bin            ,UID:1
Username: daemon         ,UID:2


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

操作符用法示例:

1.下面两语句有何不同
• awk 'BEGIN{i=0;print ++i,i}' 结果 1 1
• awk 'BEGIN{i=0;print i++,i}' 结果 0 1
实际上AWK的语法是采用VC语言风格的

2.示例:
awk中~&!~是否包含的用法:
[root@centos7 ~]awk -F: '$0 ~ /root/{print $1}' /etc/passwd
root
operator
意思是如果过滤的行中有包含root字符串的,则打印出这行的用户名

用到下文提到的patter模式,在这里是匹配是否包含root字符串
[root@centos7 ~]awk -F: '$0 ~ "/root"{print $1}' /etc/passwd
root
operator
区别上面的这个写法,在这里是包含/root字符串的行

[root@centos7 ~]awk -F: '$0 !~ /root/{print $1}' /etc/passwd
bin
daemon
adm
和上面的命令刚好相反,如果行不包含字符串root,则打印该行用户名

[root@centos7 ~]awk -F: '$3==0' /etc/passwd
root:x:0:0:root:/root:/bin/bash
意思:判断UID是否等于0,是则打印该行,判断是否为管理员

[root@centos7 ~]awk '$0~"^root"' /etc/passwd
root:x:0:0:root:/root:/bin/bash
意思:判断该行是不是以root开头的行,是则打印

3.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

4.AWK中的条件判断表达式
即三目表达式
相当于把shell中的if;then,else,fi的放到awk中
• 示例:
[root@centos7 ~]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


awk中的PATTERN和action

模式匹配和处理动作=sed的地址定界+修饰符
功能:和sed中的pattern一样起到过滤的功能,=sed的地址定界

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{}:仅在文本处理完成之后执行一次

模式:指定一个行的范围。该语法不能包括BEGIN和END模式。
BEGIN:让用户指定在第一条输入记录被处理之前所发生的动作,通常可在这里设置全局变量。
END:让用户在最后一条输入记录被读取之后发生的动作。

patter用法示例:
先写一个特殊的用法
1.当在awk命令中使用正则模式时,使用到的正则用法属于"扩展的正则表达式"
2.当使用{x,y}这种次数匹配正则表达式,需要配合--posix或者--re-interval选项
备注:这是网上找到一个示例,文中提到{x,y}必须加这个选项,然而不加选项也是可以过滤的
awk是是支持posix字符集的
[root@centos7 ~]cat f3 
seex
sex
seeex
seeeeex
[root@centos7 ~]cat f3 | awk '/se{2,3}x/{print $0}'
seex
seeex
[root@centos7 ~]cat f3 | awk -posix '/se{2,3}x/{print $0}'
seex
seeex
[root@centos7 ~]cat f3 | awk --re-interval  '/se{2,3}x/{print $0}'
seex
seeex
[root@centos7 ~]cat f3|grep  -E "se{2,3}x"
seex
seeex
[root@centos7 ~]cat f3|sed -nr '/se{2,3}x/p'
seex
seeex


1.取系统磁盘分区空间利用率df,/dev/sd开头的分区(上文中虽然取出来了,但是没过滤)
[root@centos7 ~]#df | awk -F"[[:space:]]+|%" '/^\/dev\/sd/{print $1,$5}'
/dev/sda2 8
/dev/sda3 1
/dev/sda1 17

2.取当前连接主机的IP地址(上文中虽然取出来了,但是没过滤)
[root@centos7 ~]ss -nt| awk -F"[[:space:]]+|:" '/^ESTAB/{print $6}'
192.168.34.1
192.168.34.105
或者用NF的表达方式
[root@centos7 ~]ss -nt| awk -F"[[:space:]]+|:" '/^ESTAB/{print $(NF-2)}'
192.168.34.1
192.168.34.105


3.取登录当前系统失败(lastb)用户的IP
[root@centos7 ~]#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
[root@centos7 ~]#lastb|awk '$3 ~ /[[:digit:]]/{print $3}'|sort|uniq -c
3 192.168.34.1
1 192.168.34.101
因为最后一行有btmp这样的字样,只能通过包含的功能来过滤掉最后一行

如果要取失败连接次数大于3的扔到防火墙,可以先取出来
root@centos7 ~]lastb|awk '$3 ~ /[[:digit:]]/{print $3}'|sort|uniq -c| awk '$1 >=3{print $2}'
192.168.34.1

4.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为空白,为真,对真取反,结果为假

5.awk中patter的地址定界
root@centos7 ~]#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 -nr '/^root/,/^adm/p' /etc/passwd

6.如何打印从多少行到多少行之间的行??
[root@centos7 ~]#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变向的打印出行

7.取出/etc/fstab配置文件中以UUID开头的行
[root@centos7 ~]#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 "^UUID" /etc/fstab
效果等于  sed -n '/^UUID/p' /etc/fstab

8.
awk '!0' /etc/passwd =awk '!0{print $0}' /etc/passwd 省略了{print $0}的写法
结果为真,即打印全部行

root@centos7 ~]#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
???

[root@centos7 ~]#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


9.打印奇数行和偶数行
[root@centos7 ~]#seq 6 | awk 'i=!i'  打印奇数行
1
3
5
原理:i初始值为空,为假,取反时,则打印第一行,此时i=1
i=1时,为真,取反为假,所以第二行不打印,然后i=0
依次类推所以只打印奇数行

[root@centos7 ~]#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' 打印偶数行


awk action

action除了上文中支持的算术、条件判断,表达式,还支持循环,输入语句,输出语句、组合语句等功能

• (1) Expressions:算术,比较表达式等
• (2) Control statements:if, while等
• (3) Compound statements:组合语句
• (4) input statements
• (5) output statements:print等

下面专门研究awk的控制语句包含if,while,do,for,break,continue,数组,exit等控制语句

awk中if-else控制语句的语法及用法
语法:
双分支if
if(condition){statement;…}(多条语句用;隔开)[else statement]
多分支if
if(condition1){statement1}else if(condition2){statement2}else{statement3}
使用场景:对awk取得的整行或某个字段做条件判断
if-else示例:
如判断考试分数,写法如下
[root@centos7 ~]awk -v score==80 'BEGIN{if(score <60){print "no pass"}
else if(score <=80){print "soso"}else {print good}}'
soso

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

awk中的循环不是对行的循环,因为awk本身就支持文本中行的循环,

这里是指对每一行中的字段或域或列进行循环,分别对行中的每个字段进行循环处理

awk中while循环控制语句的语法及用法

语法:while(condition){statement;…}
条件“真”,进入循环;条件“假”,退出循环
使用场景:
对一行内的多个字段逐一类似处理时使用
对数组中的各元素逐一处理时使用

此时涉及到系统自带的一个函数length(函数在下面会有介绍)
示例:
1.统计每一行第一个字段的长度
root@centos7 ~]awk -F: '{print length($1),$1}' /etc/passwd
4 root
3 bin
6 daemon
3 adm

2.统计/etc/passwd第一行的每个字段的长度
[root@centos7 ~]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那行的每个字段的长度
[root@centos7 ~]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循环
root@centos7 ~]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=9612633f-e7f1-4b28-8813-403d209d7abc 46
crashkernel=auto 16
LANG=en_US.UTF-8 16
/vmlinuz-0-rescue-d874c9913a8e4f4f8b615f29c9a0388e 50
root=UUID=9612633f-e7f1-4b28-8813-403d209d7abc 46
crashkernel=auto 16

5.面试题
用awk取出文本f5的最大值和最小值,需要先生成1000个随机数存到f5中
如何做?
1.不用awk,可以通过脚本实现最大值和最小值
2.用awk如何来做??
先生成1000个随机数
[root@centos7 ~]for i in `seq 1000`;do if [ $i -eq 1 ];then echo -e "$RANDOM\c" >> f5;
else echo -e ",$aRANDOM\c" >> f5 ;fi;done
生成了1000随机数,如何取最大值最小值?
[root@centos7 ~]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=32643 min=60

awk中do-while循环控制语句

语法:do {statement;…}while(condition)
意义:无论真假,至少执行一次循环体
do-while使用示例:
求1-100正整数的和
[root@centos7 ~]awk 'BEGIN{ total=0;i=0;do{ total+=i;i++;}while(i<=100);print total}'
5050

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正整数的和:
[root@centos7 ~]awk 'BEGIN{for(i=1;i<=100;i++)sum+=i;print sum}'
5050

2.统计grub2.cfg文件中linux16那行的字段长度大于10的字段-用for循环

[root@centos7 ~]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=9612633f-e7f1-4b28-8813-403d209d7abc 46
crashkernel=auto 16
LANG=en_US.UTF-8 16
/vmlinuz-0-rescue-d874c9913a8e4f4f8b615f29c9a0388e 50
root=UUID=9612633f-e7f1-4b28-8813-403d209d7abc 46
crashkernel=auto 16

awk中的switch控制语句

类似于shell中的case语句
语法:switch(expression) {case VALUE1 or /REGEXP/: statement1; caseVALUE2 or /REGEXP2/: statement2; ...; default: statementn}

awk中的continue,break,next控制语句

break和continue
next:
提前结束对本行处理而直接进入下一行处理(awk自身循环)

continue的示例
求1000以内偶数的和
[root@centos7 ~]awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i%2==0)continue;sum+=i}print sum}'
2500
求1000以内奇数的和
[root@centos7 ~]awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i%2==1)continue;sum+=i}print sum}'
2550
求1000以内除了66的所有数字的和
[root@centos7 ~]awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i==66)continue;sum+=i}print sum}'
4984
break的示例:
求1000数字中,当大于100时,跳出循环,即求100以内的和
[root@centos7 ~]#awk 'BEGIN{sum=0;for(i=1;i<=1000;i++){if(i>100)break;sum+=i}print sum}'
5050
next的示例:
因为提前结束对本行处理而直接进入下一行处理(因为awk本身就是循环行的功能),所以示例
打印/etc/passwd下的奇数行
[root@centos7 ~]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


awk数组-一个非常使用的功能

awk的数组全都是关联数组
关联数组:array[index-expression]
index-expression:
• (1) 可使用任意字符串;字符串要使用双引号括起来
• (2) 如果某数组元素事先不存在,在引用时,awk会自动创建此元素,并将其值
初始化为“空串”
• (3) 若要判断数组中是否存在某元素,要使用“index in array”格式进行遍历

数组的去重的效果示例:

awk '!arr[$0]++' dupfile
awk '{!arr[$0]++;print $0, arr[$0]}' dupfile
echo abc abc ccc bcd ddd ddd abc abc >> f1.txt

awk关联数组的遍历:

若要遍历数组中的每个元素,要使用for循环:因为关联数组下标是无序性
for(var in array) {for-body}
注意:var会遍历array的每个索引

数组的使用示例:

示例1.定义awk数组
[root@centos7 ~]awk 'BEGIN{title["ceo"]="zhang";title["coo"]="liu";
title["cto"]="wang";print title["ceo"]}'
zhang

可以用for循环把每一个数组的值都表示出来
[root@centos7 ~]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数组中的重要功能
    [root@centos7 ~]cat f6
    abc
    abc
    ddd
    ccc
    aaa
    ccc
    ccc
    [root@centos7 ~]awk '!line[$0]++' f6
    abc
    ddd
    ccc
    aaa
问题:为什么执行结果是这个??
原因:
awk '!line[$0]++' f6 = awk '!line[$0]++{print $0}' f6
实际上是隐藏了{print $0},这个隐藏功能在awk很重要!!!,
但是要和后面patter中的空模式区别开
'!line[$0]++'= '!line[$0]++{print $0}'是省略了写法,是patter的关系表达式中,先判断再打印
而后面的数组里的是加了双括号{},{line[$0]++}即用的patter的空模式,即文本中的每一行都处理
这两个写法是不一样的,别混淆了

分析:
基于这个考虑进行分析 '!line[$0]++' = '!line[$0]++{print $0}'
当读取文本f6的第一行时=abc
!line["abc"]++,因为abc没有值,line["abc"]=0为假,取反!line["abc"]后为真
所以把第一行的abc打印了一次,打印第一行结束后line["abc"]=1
当读取文本f6的第二行时=abc
!line["abc"]++,在之前基础上line["abc"]=1为假,取反!line["abc"]=0后为假
所以把第二行的abc不打印,结束后line["abc"]=2
以此类推,发现后续出现abc都不会打印,但是line["abc"]都会在之前的基础上+1,即达到去重并统计的目的
而处理第三行!line["ddd"]++时,和上述abc一样,第一次出现则打印,后续也不会打印
所以,命令执行结果为去重的效果

从这个命令执行结果也可以明显看到上述的分析结果
可以看出abc的值是递增的,也就是abc出现的次数
[root@centos7 ~]awk '{!line[$0]++;print $0, line[$0]}' f6
abc 1
abc 2
ddd 1
ccc 1
aaa 1
ccc 2

awk数组中的重要功能之for循环遍历数组,很具有实用性

在后面的统计服务的一些日志文件很有作用
如果要理解遍历awk数组,需要深刻理解上述的示例2:awk '!line[$0]++' f6是怎么实现的
但还是有些和数组里的,遍历$2,把$2不同的值,当成数组的下标有些区别的

若要遍历数组中的每个元素,要使用for循环

for(var in array) {for-body}
注意:var会遍历array的每个索引
为什么要通过特殊写法去遍历awk中的数组?
如果是普通数组,用循环0,1,2,3做个循环即可;而awk的数组都是关联数组,[]中括号里的下标都不固定
所以要通过 for(var in array(数组名称)) {for-body}这样的特殊写法,var的值会从array数组元素
取其下标,相当于每次循环var的值是等于array数组的下标的

注意:当我们直接引用一个数组中不存在的元素时,awk会自动创建这个元素,并且为其赋值为"空字符串"

for循环遍历数组使用示例:

示例1:
[root@centos7 ~]awk 'BEGIN{print test["ip"];test["ip"]++;print test["ip"]}'

1
第一次输出为空,第二次自动加1


示例2:
1.
[root@centos7 ~]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:
[root@centos7 ~]netstat -tan
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN     
tcp        0      0 192.168.122.1:53        0.0.0.0:*               ESTABLISTEN     
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN     
tcp        0     52 192.168.34.103:22       192.168.34.1:8816       ESTABLISHED
tcp        0      0 192.168.34.103:22       192.168.34.105:49746    ESTABLISHED
tcp6       0      0 :::111                  :::*                    LISTEN     
tcp6       0      0 :::22                   :::*                    LISTEN     
tcp6       0      0 ::1:631                 :::*                    LISTEN     
tcp6       0      0 ::1:25                  :::*                    LISTEN     

[root@centos7 ~]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数组中的下标即为各种状态,元素的值即为对应状态出现的次数,
最终,我们统计出每个状态出现的次数。


3.统计/var/log/httpd/access_log,每个IP链接的次数

root@centos7 ~]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"
192.168.34.103 - - [15/Oct/2018:22:02:35 +0800] "GET /noindex/css/bootstrap.min.css"
192.168.34.1 - - [15/Oct/2018:22:02:35 +0800] "GET /noindex/css/bootstrap.min.css"
[root@centos7 ~]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
效果等于:
[root@centos7 ~]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

4.统计ss -nt ip链接次数
[root@centos7 ~]ss -nt
State      Recv-Q Send-Q                 Local Address:Port                                Peer Address:Port              
ESTAB      0      52                    192.168.34.103:22                                  192.168.34.1:8816               
ESTAB      0      0                     192.168.34.103:22                                192.168.34.105:49746              
[root@centos7 ~]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
效果等于:
[root@centos7 ~]ss -nt|awk -F"[[:space:]]+|:" '/^ESTAB/{print $(NF-2)}'|sort|uniq -c
1 192.168.34.1
1 192.168.34.105

5.统计/etc/fstab文件系统类型分别有多少个
[root@centos7 ~]cat /etc/fstab 
# /etc/fstab
# Created by anaconda on Wed Sep 19 11:44:48 2018
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
[root@centos7 ~]cat /etc/fstab | awk '/^UUID/{file[$3]++}END{for(i in file){print i,file[i]}}'
swap 1
xfs 3

6.求下表中的男生和女生的平均成绩
[root@centos7 ~]cat f9
name sex score
a     m   90
b     f   80
c     f   99
d     m   88
e     m   80
如何利用awk的数组功能来求?
思路先求男的和和女的和?
利用两个数组?
[root@centos7 ~]cat f9|awk '!/^name/{sum[$2]+=$3}END{for(i in sum){print i,sum[i]}}' -求男女分的和
m 258
f 179
[root@centos7 ~]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

7.统计下面每个名字出现的次数
[root@centos7 ~]cat f1
Allen Phillips
Green Lee
William Aiden Janmes Lee
Angel Jack
Jack Thomas
Lucas Kevin
Tyler Lee
William Allen
[root@centos7 ~]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 


awk函数

awk也包括内置函数和自定义函数
内置函数包括 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]}

awk内置函数之sub、gsub、split实现搜索替换切割的用法

示例1:
sub(r,s,[t]):对t字符串搜索r表示模式匹配的内容,并将第一个匹配内容替换为s
gsub(r,s,[t])表示全局替换
sub(r,s,[t])不是贪婪模式,默认值替换搜索出来的第一个
[root@centos7 ~]#echo "2008:08:08 08:08:08" | awk 'sub(/:/,"-",$1)'
2008-08:08 08:08:08
只替换$1
root@centos7 ~]#echo "2008:08:08 08:08:08" | awk 'gsub(/:/,"-",$1)'
2008-08-08 08:08:08
全局替换$0
[root@centos7 ~]#echo "2008:08:08 08:08:08" | awk 'gsub(/:/,"-",$0)'
2008-08-08 08-08-08
示例2:
统计字符串中每个字母出现的次数abcdaabbccdd
[root@centos7 mnt]#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
在文章最后会用另外一种写法进行过滤(文章最后会以以空位分隔符的特殊写法来表示)
awk内置函数split的切割功能
示例1:
    统计链接本机的IP和端口号
    [root@centos7 ~]#netstat -tn
    Active Internet connections (w/o servers)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State      
    tcp        0     52 192.168.34.103:22       192.168.34.1:8816       ESTABLISHED
    tcp        0      0 192.168.34.103:22       192.168.34.105:49746    ESTABLISHED
    [root@centos7 ~]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
    [root@centos7 ~]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当下标,就统计每个端口号出现的次数

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中的函数类似

awk中很实用的内置函数system命令

system函数作用:在awk可以反过来调用linux里的命令
示例:
    空格是awk中的字符串连接符,如果system中需要使用awk中的变量可以使用
    空格分隔,或者说除了awk的变量外其他一律用""引用起来

示例1:
显示/boot/grub2下的文件列表;调用命令时要加双引号
[root@centos7 ~]#awk 'BEGIN{system("ls /boot/grub2")}'
device.map  fonts  grub.cfg  grubenv  i386-pc  locale
或者这么写,先定义变量等于路径,再调用变量
[root@centos7 ~]#awk 'BEGIN{dir="/boot/grub2";system("ls "dir)}'
device.map  fonts  grub.cfg  grubenv  i386-pc  locale
调用hostname命令,显示主机名
[root@centos7 ~]#awk 'BEGIN{system("hostname")}'
centos7.localdomain

示例2:
之前处理过lastb中登录失败的IP,如果失败登录次数大于3次,扔到防火墙里,
当时是先取出IP放到文件里,然后iptables再禁用;
现在可以在awk中先取出IP,然后通过system("iptables " IP)就可以直接扔到防火墙中

具体实现?

awk脚本

将awk程序写成脚本,直接调用或执行

awk脚本使用示例:
1.先写文本再调用
cat f1.awk
{if(1,$3}
awk -F: -f f1.awk /etc/passwd

2.也可以写成脚本形式
先写再调用
[root@centos7 ~]vim f2.awk
#!/bin/awk -f
{if($3 >=1000)print $1,$3} 

[root@centos7 ~]./f2.awk -F: /etc/passwd
nfsnobody 65534
test 1000
gentoo 1007
nginx 1008
ceshi 1009

向awk脚本传递参数

格式:
awkfile 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

工作中遇到的常用awk文本解决案例:

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
网站日志分析篇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'
网站日分析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]}}'
安全篇:(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如下格式,请提取”.magedu.com”前面的主机名部分并写
入到回到该文件中
1 blog.magedu.com
2 www.magedu.com
…
999 study.magedu.com

2、统计/etc/fstab文件中每个文件系统类型出现的次数
[root@centos7 ~]cat /etc/fstab | awk '/^UUID/{print $3}'|sort|uniq -c |sort -nr
      3 xfs
      1 swap
[root@centos7 ~]cat /etc/fstab | awk '/^UUID/{file[$3]++}END{for(i in file){print i,file[i]}}'
swap 1
xfs 3

3、统计/etc/fstab文件中每个单词出现的次数
root@centos7 ~]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中的所有数字
[root@centos7 ~]echo "Yd$C@M05MB%9&Bdh7dq+YVixp3vpw"|awk 'gsub(/[^0-9]/,"",$0)'
05973
[root@centos7 ~]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
[root@centos7 ~]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
[root@centos7 ~]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
[root@centos7 ~]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
[root@centos7 mnt]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
下面的写法在双引号前一定要加一个空格才能匹配出来
或者用单引号,但是也需要在前面几个空格
[root@centos7 mnt]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
或者用单引号,但是也需要在前面几个空格
[root@mini7-1 ~]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

root@centos7 mnt]echo abcdaabbccdd | grep -o "[a-z]"|sort|uniq -c
      3 a
      3 b
      3 c
      3 d

10.面试题:取出/etc/fstab中的挂载目录
    [root@node7-1 data]cat /etc/fstab | awk -F "[ ]/?" '/^UUID/{print $2}'
    boot
    data
    swap
    [root@node7-1 data]cat /etc/fstab | awk -F "[ ]/|[ ]" '/^UUID/{print $2}'
    boot
    data
    swap

AWK中的输入分隔符

我们可以使用两次awk -F命令,每次分别指定一个分隔符来进行操作,但是这样太麻烦,还有更简单的方法,即一次指定多个分隔符。
要一次指定多个分隔符,需要将分隔符用中括号[]包裹起来,如果多个分隔符中有至少一个特殊字符,那么还需要在中括号外加上双引号或者单引号,并且使用两个或两个以上的\将其进行转义
$、^、(、)、[、]、?、.、|

示例1:
        [root@node7-1 data]cat b.txt 
        ssh:[email protected]
        ssh:[email protected]
        ssh:[email protected]
    1.取user和IP
        [root@node7-1 data]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中的:和@换成^和|,又该怎么取?
        [root@node7-1 data]awk -F '[\\^\\|]' '{print $2,$3}' b.txt 
        user1 192.168.1.10
        user2 192.168.1.11
        user3 192.168.1.12
示例2:
    示例1:
        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

11.取出文本中的姓名和FQDN,awk的方法参照文章最上面的链接文章
    [root@node7-1 data]cat a.txt 
    xiaoming\t20\thttp://sougou.com
    xiaohua\t25\thttp://www.baidu.com
    xiaodong\t30\thttp://www.jidong.com
方法一:用awk取,以\t做分隔符,但是在表示方法上比较特殊
    root@node7-1 data]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取
    [root@node7-1 data]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
12.扩展11题的内容把t换成$,又该如何取?
    [root@node7-1 data]cat a.txt 
    xiaoming\$20\$http://sougou.com
    xiaohua\$25\$http://www.baidu.com
    xiaodong\$30\$http://www.jidong.com
方法一:还是只用awk来取
    [root@node7-1 data]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取
    [root@node7-1 data]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中的$需要转义

你可能感兴趣的:(文本处理三剑客之awk)