我理解的awk及常用操作

awk的命令简直太强大了,可以写一本书专门来描述它,在这里我们介绍一下它的几个常用场景,平常足够咱们使用即可,体验它的强大之处我们可以看看命令帮助。

awk是一个文本分析工具,awk是将文件逐行读入,默认分隔符(空格、制表符),使用分隔符进行切片,之后再对数据进行提取处理等等。下面举的例子基本上都是很常用的,小伙伴多多尝试。

工作流程:

awk [-F] 'BEGIN{action} [pattern]{action...} END{action...}'
#-F为分隔符,默认是空格和制表符,我们可以定义为 -F":",
#awk会根据分隔符将行分成若干个字段,$0为整行,$1为第一个字段,$2 为第2个地段,依此类推…

#第一步:执行BEGIN中的action语句块,可以是初始化变量,可以if语句,打印输出表格的表头等等

#第二步:从文件或标准输入(stdin)读取一行,然后执行pattern{action… }语句块,它逐行扫描文件,从第 一行到#最后一行重复这个过程,直到文件全部被读取完毕

#第三步:当读至输入流末尾时,执行END{action…}语句块,END语句块在awk从输入流中读取完所有的行之后即被执##行,比如打印所有行的分析结果这类信息汇总

注意:BEGIN 和END 都可以省略的

awk内置变量:

  • FNR 浏览文件的记录数,即行数
  • NF 浏览记录的字段个数,即被分隔符 分割后的字段数
  • NR 已读的记录数
  • FS 设置输入域分隔符,等价于-F的选项值
  • OFS 指定输出时的分隔符
  • FILENAME awk浏览的文件名

awk支持函数:

  • toupper(str) 返回str的大写
  • tolower(st r) 返回str的小写
  • length(str) 返回str长度
  • substr(str,p) 返回字符串str中从p开始的后缀部分
  • split(str,arr,splitter) 返回切割后数组长度

小试牛刀

输出系统中所有用户

cat /etc/passwd |awk -F":" '{print $1}'|nl

输出系统中 bin/bash 用户:

cat /etc/passwd |awk -F":" '/bin\/bash/{print $1}'|nl

我们使用正则匹配,格式 /pattern/
输出/etc/passwd的第1列和第7列,用逗号分隔显示,开始行输出’first’结束行输出’end’

cat /etc/passwd |awk -F":" 'BEGIN{print "first"}/bin\/bash/{print $1 ","  $NF}END{print "end"}'

统计/etc/passwd文件中,每行的行号,每行的列数,对应的完整行内容

cat /etc/passwd |awk -F":" '{ print NR " " NF " " $0 }' 

输出字符串的长度:

awk 'BEGIN { print length("hello world) }'

awk中常用条件操作符:

  • < 小于
  • <= 小于等于
  • == 等于
  • != 不等于
  • ~ 匹配正则表达式
  • !~ 不匹配正则表达式

使用案例:

匹配/etc/passwd 第三域大于100的显示出完整信息:

cat /etc/passwd | awk -F ":" '{if($3 > 100) {print $0}}'

打印行号> 20的,并且最后一域匹配bash的信息.

cat /etc/passwd | awk -F ":" '{if($NR > 20 && $NF~/bash/) {print $0}}'

打印出第三域数字之和

cat /etc/passwd | awk -F ":" 'BEGIN{sum =0} {sum = sum+$3} END {print sum}'

请匹配bin/bash结尾的用户有多少条

cat /etc/passwd | awk -F ":" '/bin\/bash/{print $0}'|wc -l

cat /etc/passwd|awk -F ":" '{if( $NF~/bash/) {i++}} END {print i }'

cat /etc/passwd |awk 'BEGIN{num=0}/bin\/bash/{num++}END{print num}'

找出UID 大于 500的用户:

awk -F ":" '{if($3 >500) print "line:"NR "\t" "cols:"NF "\t" "user:"$1 "\t"FILENAME}'  /etc/passwd

找出r 开头的用户

awk -F":" '$1~/^r.*/{print $0}' /etc/passwd

我们可以使用 -f 从一个文本中读取脚本,scriptfile文件内容:

{print $1 "家目录:" $6}

我们来使用它:

awk -F":" -f scriptfile /etc/passwd
root家目录:/root
bin家目录:/bin
daemon家目录:/sbin
adm家目录:/var/adm
lp家目录:/var/spool/lpd
...

指定输出时的分隔符:

awk  'BEGIN{FS=":",OFS="--"}/bin\/bash/{print $1,$2}' /etc/passwd
root--x
houger--x

awk在nginx日志的几个案例分析:


我们想要2019年01月18号,访问错误日志,并重定向到一个文件中:

cat nginx_error.log|grep '2019/01/18' > /tmp/0118.log

查看访问量前 5 的用户IP,假设ip在第一列:

awk '{print $1}' nginx_error.log|sort|uniq -c|sort -nr|head -n 5

注:上述命令我们首先获取第一列 ip,默认排序之后进行去重统计,然后按照访问量倒序排序,取前 5 条

上述代码的第二种写法,我们使用数组结合 END语句块实现

awk '{arr[$1]++}END{for(i in arr) print arr[i],i|"sort -rnk 1|head -5"}' nginx_error.log

上述代码,我们将ip地址作为数组下标(键),使用运算符++作为数组元素值,元素初始值为 0。每读取一行ip,就会以该ip作为数组下标,执行元素 ++操作。如果这个ip在数组中已经存在,则元素值再+1,也就是说重复ip值会一直做+1。以此类推可以实现去重操作,因为数组中不存在重复的。在awk语块中可以使用管道符,使用linux命令再次处理数据,管道符后面命令需要用双引号包裹


根据IP统计访问UV:

awk '{print $1}' nginx_access.log | sort | uniq -c | wc -l

根据访问url统计PV量:

awk '{print $6}' nginx_access.log | wc -l

统计PV量,不需要去重,每一次点击即被计算一次。


统计日志中访问量大于 10次 的ip用户

第一种写法,我们可以在上述写法后面再写一个awk 语句判断一下:

 awk '{print $1}' access.log|sort|uniq -c|sort -nr| awk '{if ($1 >10) print $0}'

另一种写法:我们可以在END语句块中使用if语句判断:

awk '{arr[$1]++}END{for(i in arr){if(arr[i] > 10) print arr[i],i |"sort -rnk 1"}}'  access.log

统计每个URL访问内容的总大小以及访问数量($body_bytes_sent):

awk '{url[$7]++;size[$7]+=$10}END{for(i in url) print url[i],i,size[i]}' access.log

注:$7 为访问url 地址列,$10 为响应页面大小列。我们定义了两个数组,它们都以url地址为数组下标:url 统计访问url的数量,size计算页面的大小。


统计每个IP访问的状态码数量($status 第九列):

awk '{arr[$1"\t"$9]++}END{for(i in arr) print i,arr[i]}' access.log

我们将IP状态码作为数组下标,保证唯一,在END语块中打印出每个ip对应状态码的访问数量


统计访问状态码为404的IP及出现次数:

awk '{if($9~/404/)arr[$1 "\t"$9]++}END{for(i in arr) print i,arr[i]}' access.log

我们在读取语句块中加入if 正则判断,匹配404状态码

参考:
awk-command

发表评论

电子邮件地址不会被公开。 必填项已用*标注