经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » Linux/Shell » 查看文章
Linux文本处理三剑客之awk学习笔记12:实战演练
来源:cnblogs  作者:阿龙弟弟  时间:2021/2/18 15:24:09  对本文有异议

此博文的例题来源于骏马金龙的awk课程以及awk示例的整合。一些在以往的awk学习笔记中有涉及的示例,这里就不再重复了。

处理代码注释

  1. # cat comment.txt
  2. /*AAAAAAAAAA*/ # 整行都被注释所占满。
  3. 1111
  4. 222
  5.  
  6. /*aaaaaaaaa*/
  7. 32323
  8. 12341234
  9. 12134 /*bbbbbbbbbb*/ 132412 # 注释的左右两边有内容,需保留。
  10. 14534122
  11. /* # 跨行注释。
  12. cccccccccc
  13. */
  14. xxxxxx /*ddddddddddd # 跨行注释且注释的左边有内容,需保留。
  15. cccccccccc
  16. eeeeeee
  17. */ yyyyyyyy # 跨行注释且注释的右边有内容,需保留。
  18. 5642341

需要充分理解哪些是应该删除的,哪些是应该保留的。

  1. # cat comment.awk
  2. index($0,"/*"){
  3. if(index($0,"*/")){ # 同行包含“*/”字符串。
  4. # 12134 /*bbbbbbbbbb*/ 132412
  5. print gensub("^(.*)/\\*.*\\*/(.*)$","\\1\\2","g")
  6. }else{ # 同行不包含“*/”字符串。
  7. print gensub("^(.*)/\\*.*$","\\1","g")
  8. while(getline){
  9. if(index($0,"*/")){
  10. print gensub("^.*\\*/(.*)$","\\1","g")
  11. next # 这里不能使用break。请理解它们的区别。
  12. }
  13. }
  14. }
  15. }
  16. !index($0,"/*"){
  17. print
  18. }
  19. # awk -f comment.awk comment.txt
  20. 1111
  21. 222
  22.  
  23.  
  24. 32323
  25. 12341234
  26. 12134 132412
  27.  
  28. 14534122
  29. xxxxxx
  30. yyyyyyyy
  31. 5642341

这个代码还有一些可以优化的点,例如去除空白行与空行。

 

前后段落判断

有这样的一个文件。

  1. # cat order.txt
  2. 2019-09-12 07:16:27 [-][
  3. 'data' => [
  4. 'http://192.168.100.20:2800/api/payment/i-order',
  5. ],
  6. ]
  7. 2019-09-12 07:16:27 [-][
  8. 'data' => [
  9. false,
  10. ],
  11. ]
  12. 2019-09-21 07:16:27 [-][
  13. 'data' => [
  14. 'http://192.168.100.20:2800/api/payment/i-order',
  15. ],
  16. ]
  17. 2019-09-21 07:16:27 [-][
  18. 'data' => [
  19. 'http://192.168.100.20:2800/api/payment/i-user',
  20. ],
  21. ]
  22. 2019-09-17 18:34:37 [-][
  23. 'data' => [
  24. false,
  25. ],
  26. ]

由多段构成,每一段的格式类似如下:

  1. YYYY-MM-DD HH:mm:SS [-][
  2. 'data' => [
  3. 'URL',
  4. ],
  5. ]

需求:找出段信息包含“false”并且它的前一段包含“i-order”,然后将符合条件的这两段信息打印出来。

思路:

  • 文本信息具有规律性,修改RS使得每段信息成为一条记录。
  • 需要定义一个变量来保存前一段信息。
  • 当前段信息和前一段信息需要同时满足条件。
  1. # cat order.awk
  2. BEGIN{
  3. ORS=RS="]\n"
  4. }
  5. {
  6. if($0~/false/&&prev~/i-order/){ # 只有第一条记录的$0会和prev相同。如果第一条记录同时包含了“false”和“i-order”,那么就要另作考虑了。
  7. print prev
  8. print $0
  9. }
  10. prev=$0
  11. }
  12. # awk -f order.awk order.txt
  13. 2019-09-12 07:16:27 [-][
  14. 'data' => [
  15. 'http://192.168.100.20:2800/api/payment/i-order',
  16. ],
  17. ]
  18. 2019-09-12 07:16:27 [-][
  19. 'data' => [
  20. false,
  21. ],
  22. ]

 

行列转换

示例一

这道题我个人认为是比较经典的一道题目,尤其是进阶版的考察了awk的许多方面。

首先我们来看基础版,也就是作者的原版。

  1. # cat RowColumnConvert.txt
  2. ID name gender age email
  3. 1 Bob male 28 qq.com
  4. 2 Alice female 20 163.com
  5. 3 Tony male 18 gmail.com
  6. 4 Kevin female 30 xyz.com

期望将行转换成列。

  1. ID 1 2 3 4
  2. name Bob Alice Tony Kevin
  3. gender male female male female
  4. age 28 20 18 30
  5. email qq.com 163.com gmail.com xyz.com

原作者给出的答案。

  1. # cat RowColumnConvert.awk
  2. {
  3. for(i=1;i<=NF;i++){
  4. if(typeof(arr[i])=="unassigned"){
  5. arr[i]=$i
  6. }else{
  7. arr[i]=arr[i]"\t"$i
  8. }
  9. }
  10. }
  11. END{
  12. for(i=1;i<=NF;i++){
  13. print arr[i]
  14. }
  15. }

这种使用字符串连接再在其中加入一个制表符来构建的方式,如果某些记录的长度过长或者过短,就会导致排版的不统一。

在该示例中则是原第5行第4列“gmail.com”长度过长导致的。

这个代码要求每一行同字段之间的长度不可以太长。

因此我们来看一下进阶版,要求行列转换以后要对齐。

  • 首先需要先将原始数据保存起来,然后再输出。原始数据由第N行第N列以及其对应的具体值来表述,例如“第3行第3列是female”,那么需要存储的信息就有3个,就可以使用二维数组。
  • 使用变量i表示原始数据的行,变量j表示原始数据的列。在脑中要有这样的思路,不然很容易出错。
  • 原文件行数和列数一致,容易造成误导,最好修改一下,使它们不一致。
  • 对齐的思路是我们去计算应该填充多少空格字符。
  1. # cat RowColumnConvert2.awk
  2. {
  3. for(j=1;j<=NF;j++){
  4. arr[NR,j]=$j
  5. len[j]=length($j)
  6. maxLength[NR]=len[j]>maxLength[NR]?len[j]:maxLength[NR]
  7. }
  8. }
  9. func cat(count ,str,x){ # 这里的“局部变量”的定义很重要,尤其是如果这里使用了同名变量i或者j的情况下!
  10. for(x=1;x<=count;x++){
  11. str=str" "
  12. }
  13. return str
  14. }
  15. END{
  16. for(j=1;j<=NF;j++){
  17. for(i=1;i<=NR;i++){
  18. if(typeof(brr[j])=="unassigned"){
  19. brr[j]=arr[i,j]""cat(maxLength[i]-length(arr[i,j]))" "
  20. }else{
  21. brr[j]=brr[j]""arr[i,j]""cat(maxLength[i]-length(arr[i,j]))" "
  22. }
  23. }
  24. print brr[j]
  25. }
  26. }
  27. # awk -f RowColumnConvert2.awk RowColumnConvert.txt
  28. ID 1 2 3 4 5
  29. name Bob Alice Tony Kevin Tom
  30. gender male female male female male
  31. age 28 20 18 30 25
  32. email qq.com 163.com gmail.com xyz.com alibaba.com

示例二

  1. name age
  2. alice 21
  3. ryan 30

期望转换成:

  1. name alice ryan
  2. age 21 30
  1. # cat RowColumnConvert3.awk
  2. {
  3. for(i=1;i<=NF;i++){
  4. if(typeof(arr[i])=="unassigned"){
  5. arr[i]=$i
  6. }else{
  7. arr[i]=arr[i]" "$i
  8. }
  9. }
  10. }
  11. END{
  12. for(i=1;i<=NF;i++){
  13. print arr[i]
  14. }
  15. }
  16. # awk -f RowColumnConvert3.awk test.txt
  17. name alice ryan
  18. age 21 30

示例三

  1. # cat test.txt
  2. 74683 1001
  3. 74683 1002
  4. 74683 1011
  5. 74684 1000
  6. 74684 1001
  7. 74684 1002
  8. 74685 1001
  9. 74685 1011
  10. 74686 1000
  11. 100085 1000
  12. 100085 1001

期望输出:

  1. 74683 1001 1002 1011
  2. 74684 1000 1001 1002
  3. 74685 1001 1011
  4. 74686 1000
  5. 100085 1000 1001
  1. # cat RowColumnConvert4.awk
  2. {
  3. if(!$1 in arr){
  4. arr[$1]=$2
  5. }else{
  6. arr[$1]=arr[$1]" "$2
  7. }
  8. }
  9. END{
  10. for(i in arr){
  11. print i,arr[i]
  12. }
  13. }
  14. # awk -f RowColumnConvert4.awk test.txt
  15. 74683 1001 1002 1011
  16. 74684 1000 1001 1002
  17. 74685 1001 1011
  18. 74686 1000
  19. 100085 1000 1001

 

格式化空白字符

主要涉及awk对于$N进行修改时会基于OFS来重建$0。在【字段与记录的重建】中我们已经提到过。

  1. # cat chaos.txt
  2. aaa bb cccc
  3. dd ee ff gg
  4. hhhhh i jjjj
  5. # awk 'BEGIN{OFS="\t"}{$1=$1;print}' chaos.txt
  6. aaa bb cccc
  7. dd ee ff gg
  8. hhhhh i jjjj

在Linux中是对齐的,不晓得是不是博客园【插入代码】显示的问题。

 

筛选IP地址

目标是从ifconfig的输出结果中筛选出IPv4地址。这题我们以前就做过,具体的解题思路详见读取文件中的【数据筛选示例】,这里直接给答案。

  1. ifconfig | awk '/inet /&&!/127.0.0.1/{print $2}'
  2. ifconfig | awk 'BEGIN{RS=""}!/^lo/{print $6}'
  3. ifconfig | awk 'BEGIN{RS="";FS="\n"}!/^lo/{FS=" ";$0=$2;print $2;FS="\n"}'

 

读取配置文件中的某段

这里我们以yum源的配置文件为例。我们过滤掉注释和空行。

  1. # grep -vE "^#|^$" /etc/yum.repos.d/CentOS-Base.repo
  2. ... ...
  3. [extras]
  4. name=CentOS-$releasever - Extras
  5. mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=extras&infra=$infra
  6. gpgcheck=1
  7. gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
  8. ... ...

期望仅取出某一段数据,例如[extras]段。

思路一:

  • 配置文件具备规律性,将中括号作为记录分隔符。
  • 基于上面那点再修修补补即可取到想要的信息。
  1. # grep -vE "^#|^$" /etc/yum.repos.d/CentOS-Base.repo | awk 'BEGIN{RS="[";ORS=""}/^extras/{print "["$0}'
  2. [extras]
  3. name=CentOS-$releasever - Extras
  4. mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=extras&infra=$infra
  5. gpgcheck=1
  6. gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

思路二:

  • 先找extras那行,找到以后输出。
  • 随后循环getline并打印,直到遇到下一个配置段“[.+]”。
  1. # cat extract.awk
  2. index($0,"[extras]"){
  3. print
  4. while((getline)>0){
  5. if($0~/\[.+\]/){
  6. break
  7. }
  8. print
  9. }
  10. }
  11. # grep -vE "^#|^$" /etc/yum.repos.d/CentOS-Base.repo | awk -f extract.awk
  12. [extras]
  13. name=CentOS-$releasever - Extras
  14. mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=extras&infra=$infra
  15. gpgcheck=1
  16. gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

 

根据$0中的部分信息进行去重

首先来看示例文件。

  1. # cat partDuplicate.txt
  2. 2019-01-13_12:00_index?uid=123
  3. 2019-01-13_13:00_index?uid=123
  4. 2019-01-13_14:00_index?uid=333
  5. 2019-01-13_15:00_index?uid=9710
  6. 2019-01-14_12:00_index?uid=123
  7. 2019-01-14_13:00_index?uid=123
  8. 2019-01-15_14:00_index?uid=333
  9. 2019-01-16_15:00_index?uid=9710

如果问号后面的“uid=xxx”相同,我们就认为是重复的数据,并且将其去除。

输出的时候,我们要保证原本的数据出现的顺序,因此就不应存入数组并进行无序遍历了。

思路在数组的实战中我们就有接触过了。

思路一:

以问号作为FS,将$2作为数组索引,每次awk内部循环对arr[$2]进行自增,第一次出现的数据arr[$2]的值就为1,仅针对第一次出现的数据进行输出即可。

  1. # awk 'BEGIN{FS="?"}{arr[$2]++;if(arr[$2]==1){print}}' partDuplicate.txt
  2. 2019-01-13_12:00_index?uid=123
  3. 2019-01-13_14:00_index?uid=333
  4. 2019-01-13_15:00_index?uid=9710

思路二:

我们可以将“!arr[$2]++”拿来做pattern,第一次出现数据时返回值为1,往后的返回值均是0。

action部分只需要输出,并且以下三者等价:

  1. PAT{print $0}
  2. PAT{print}
  3. PAT

关于pattern和action的省略情况,详见这里。因此我们就只需要pattern即可。

  1. # awk 'BEGIN{FS="?"}!arr[$2]++' partDuplicate.txt
  2. 2019-01-13_12:00_index?uid=123
  3. 2019-01-13_14:00_index?uid=333
  4. 2019-01-13_15:00_index?uid=9710

 

次数统计

示例文件:

  1. # cat test.txt
  2. portmapper
  3. portmapper
  4. portmapper
  5. portmapper
  6. portmapper
  7. portmapper
  8. status
  9. status
  10. mountd
  11. mountd
  12. mountd
  13. mountd
  14. mountd
  15. mountd
  16. nfs
  17. nfs
  18. nfs_acl
  19. nfs
  20. nfs
  21. nfs_acl
  22. nlockmgr
  23. nlockmgr
  24. nlockmgr
  25. nlockmgr
  26. nlockmgr
  1. # awk '{arr[$0]++}END{for(i in arr){print i"-->"arr[i]}}' test.txt
  2. nfs-->4
  3. status-->2
  4. nlockmgr-->5
  5. portmapper-->6
  6. nfs_acl-->2
  7. mountd-->6

 

统计TCP连接状态数量

详见数组的实战部分。

 

根据http状态码统计日志中各IP的出现次数

需求:统计web日志中,http状态码非200的客户端IP的出现次数,按照降序的方式统计出前10行。

日志文件放百度网盘了,提取码是jtlg。

  1. 111.202.100.141 - - [2019-11-07T03:11:02+08:00] "GET /robots.txt HTTP/1.1" 301 169 "-" "Sogou web spider/4.0(+http://www.sogou.com/docs/help/webmasters.htm#07)" "-"
  1. # awk '$8!=200{arr[$1]++} END{PROCINFO["sorted_in"]="@val_num_desc";for(i in arr){if(cnt++==10){break}print arr[i]"-->"i}}' access.log
  2. 896-->60.21.253.82
  3. 75-->216.83.59.82
  4. 21-->211.95.50.7
  5. 21-->61.241.50.63
  6. 20-->59.36.132.240
  7. 18-->182.254.52.17
  8. 16-->50.7.235.2
  9. 15-->101.89.19.140
  10. 15-->94.102.50.96
  11. 13-->198.108.67.80

 

统计独立IP

  1. # cat independence.txt
  2. a.com.cn|202.109.134.23|2015-11-20 20:34:43|guest
  3. b.com.cn|202.109.134.23|2015-11-20 20:34:48|guest
  4. c.com.cn|202.109.134.24|2015-11-20 20:34:48|guest
  5. a.com.cn|202.109.134.23|2015-11-20 20:34:43|guest
  6. a.com.cn|202.109.134.24|2015-11-20 20:34:43|guest
  7. b.com.cn|202.109.134.25|2015-11-20 20:34:48|guest

从该文件中统计每个域名及其对应的独立IP数。

例如,a.com.cn的行有3条,但是独立IP只有2个。因此需要记录的信息就是:

  1. a.com.cn 2

将所有的域名及其独立IP的数量统计后输出到“域名.txt”格式的文件中。

  1. # awk 'BEGIN{FS="|"} !arr[$1,$2]++{brr[$1]++} END{for(i in brr){print i,brr[i]>i".txt"}}' independence.txt
  2. # cat a.com.cn.txt
  3. a.com.cn 2
  4. # cat b.com.cn.txt
  5. b.com.cn 2
  6. # cat c.com.cn.txt
  7. c.com.cn 1

 

两个文件的处理

存在两个文件file1.txt和file2.txt:

  1. # cat file1.txt
  2. 50.481 64.634 40.573 1.00 0.00
  3. 51.877 65.004 40.226 1.00 0.00
  4. 52.258 64.681 39.113 1.00 0.00
  5. 52.418 65.846 40.925 1.00 0.00
  6. 49.515 65.641 40.554 1.00 0.00
  7. 49.802 66.666 40.358 1.00 0.00
  8. 48.176 65.344 40.766 1.00 0.00
  9. 47.428 66.127 40.732 1.00 0.00
  10. 51.087 62.165 40.940 1.00 0.00
  11. 52.289 62.334 40.897 1.00 0.00
  12. # cat file2.txt
  13. 48.420 62.001 41.252 1.00 0.00
  14. 45.555 61.598 41.361 1.00 0.00
  15. 45.815 61.402 40.325 1.00 0.00
  16. 44.873 60.641 42.111 1.00 0.00
  17. 44.617 59.688 41.648 1.00 0.00
  18. 44.500 60.911 43.433 1.00 0.00
  19. 43.691 59.887 44.228 1.00 0.00
  20. 43.980 58.629 43.859 1.00 0.00
  21. 42.372 60.069 44.032 1.00 0.00
  22. 43.914 59.977 45.551 1.00 0.00

需求:替换file2.txt的第5列的值为file2.txt的第1列减去file1.txt的第1列的值。

方法一

  1. # cat twoFile1.awk
  2. {
  3. num1=$1
  4. if((getline < "file2.txt")>0){
  5. $5=$1-num1
  6. print $0
  7. }
  8. }
  9. # awk -f twoFile1.awk file1.txt
  10. 48.420 62.001 41.252 1.00 -2.061
  11. 45.555 61.598 41.361 1.00 -6.322
  12. 45.815 61.402 40.325 1.00 -6.443
  13. 44.873 60.641 42.111 1.00 -7.545
  14. 44.617 59.688 41.648 1.00 -4.898
  15. 44.500 60.911 43.433 1.00 -5.302
  16. 43.691 59.887 44.228 1.00 -4.485
  17. 43.980 58.629 43.859 1.00 -3.448
  18. 42.372 60.069 44.032 1.00 -8.715
  19. 43.914 59.977 45.551 1.00 -8.375

方法二

我们期望将file1.txt和file2.txt都直接作为命令的参数。形如:

  1. awk '...rule...' file1.txt file2.txt
  1. # cat twoFile2.awk
  2. NR==FNR{ # 如果NR和FNR相等,那么就表示awk在处理的文件是第一个文件
  3. arr[FNR]=$1
  4. }
  5. NR!=FNR{
  6. $5=$1-arr[FNR]
  7. print $0
  8. }
  9. # awk -f twoFile2.awk file1.txt file2.txt
  10. 48.420 62.001 41.252 1.00 -2.061
  11. 45.555 61.598 41.361 1.00 -6.322
  12. 45.815 61.402 40.325 1.00 -6.443
  13. 44.873 60.641 42.111 1.00 -7.545
  14. 44.617 59.688 41.648 1.00 -4.898
  15. 44.500 60.911 43.433 1.00 -5.302
  16. 43.691 59.887 44.228 1.00 -4.485
  17. 43.980 58.629 43.859 1.00 -3.448
  18. 42.372 60.069 44.032 1.00 -8.715
  19. 43.914 59.977 45.551 1.00 -8.375

 

原文链接:http://www.cnblogs.com/alongdidi/p/awkTraining.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号