awk正则表达式技巧(匹配每行中第一次出现的字符)

3

我一直在为这个问题苦恼,希望有一个简单的解决方案,但我可能错过了。

摘要

简化后的以下代码无法处理解析到其中的IPv6地址。我应该在解析到AWK之前对变量进行SED操作,还是可以更改AWK正则表达式以仅匹配$clog中每行的第一个“:”?

$ clog='djerk.nl:80 200.87.62.227 - - [20/Nov/2015:01:06:25 +0100] "GET /some_url HTTP/1.1" 404 37252
bogus.com:80 200.87.62.227 - - [20/Nov/2015:01:06:27 +0100] "GET /some_url HTTP/1.1" 404 37262
djerk.nl:80 200.87.62.227 - - [20/Nov/2015:01:06:29 +0100] "GET /another_url HTTP/1.1" 200 11142
ipv6.com:80 2a01:3e8:abcd:320::1 - - [20/Nov/2015:01:35:24 +0100] "GET /some_url HTTP/1.1" 200 273'

$ echo "$clog" | awk -F '[: -]+' '{ vHost[$1]+=$13 } END { for (var in vHost) { printf "%s %.0f\n", var, vHost[var] }}'
> bogus.com 37262
> djerk.nl 48394
> ipv6.com 0

可以看到变量$clog的最后一行,捕获了虚拟主机域名,但字节数应该为273而不是0。 原始复杂问题: 我遇到的问题与“:”字符有关。除了其他两个字符(空格和破折号)之外,我需要AWK仅匹配它正在评估的每行中第一个“:”出现的位置。下面通过三个字符拆分每行,这很好用,直到日志条目包含IPv6地址。
matrix=$( echo "$clog" | awk -F '[: -]+' '{ vHost[$1]++; Bytes[$1]+=$13 } END { for (var in vHost) { printf "%s %.0f %.0f\n", var, vHost[var], Bytes[var] }}' )

以上代码将转换以下日志条目(包含在变量 $clog 中):
djerk.nl:80 200.87.62.227 - - [20/Nov/2015:01:06:25 +0100] "GET /some_url HTTP/1.1" 404 37252 "-" "Safari/11601.1.56 CFNetwork/760.0.5 Darwin/15.0.0 (x86_64)"
bogus.com:80 200.87.62.227 - - [20/Nov/2015:01:06:27 +0100] "GET /some_url HTTP/1.1" 404 37262 "-" "Safari/11601.1.56 CFNetwork/760.0.5 Darwin/15.0.0 (x86_64)"
djerk.nl:80 200.87.62.227 - - [20/Nov/2015:01:06:29 +0100] "GET /wordpress/2014/ssl-intercept-headaches HTTP/1.1" 200 11142 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 8_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12B410 Safari/600.1.4"
djerk.nl:80 200.87.62.227 - - [20/Nov/2015:01:06:30 +0100] "GET /some_other_url HTTP/1.1" 404 37264 "-" "Safari/11601.1.56 CFNetwork/760.0.5 Darwin/15.0.0 (x86_64)"

将其转换为类似下面的表格,包含虚拟主机名称(不包括TCP端口号)、点击量和累积字节数。每个虚拟主机一行:
djerk.nl 3 85658
bogus.com 1 37262

但是由于IPv6地址的表示方式,它们会被无意中分割,导致AWK在评估这些日志条目时产生虚假输出。示例IPv6日志条目:

djerk.nl:80 2a01:3e8:abcd:320::1 - - [20/Nov/2015:01:35:24 +0100] "POST /wordpress/wp-cron.php?doing_wp_cron=*** HTTP/1.0" 200 273 "-" "WordPress; http://www.djerk.nl/wordpress"

我想一个解决办法是对变量 $clog 进行处理,替换第一次出现的 ":" 并从 AWK 正则表达式中删除该字符。但我认为原生的 bash 字符串替换无法处理多行变量。

clog=$(sed 's/:/ /' <<< "$clog")
matrix=$( echo "$clog" | awk -F '[ -]+' '{ vHost[$1]++; Bytes[$1]+=$10 } END { for (var in vHost) { printf "%s %.0f %.0f\n", var, vHost[var], Bytes[var] }}' )

这是因为$clog被引用,保留了换行符,并在每行上单独运行sed。结果(如所示),需要调整AWK行以忽略“:”,并抓取$10而不是$13的字节计数。
所以事实证明,在编写本文时,我已经给自己找到了一个解决方案。但我相信有人会知道更好、更有效的方法。

3
有没有办法你能简化一下,让我们能够轻松理解,然后再根据你的实际问题来调整解决方案?上面的文字太多了,我没办法费力去理解所有内容,而且我猜其他人也会有同样的感觉。 - undefined
2
如果你在每一行输出$1,$13,你会发现问题即使没有IPv6地址也会出现。 - undefined
2
在提取之前将日志文件捕获到一个变量中看起来很可疑。通常情况下,你会希望在文件或套接字的字节流上运行Awk,而不是在你捕获的变量上运行。 - undefined
2
在内存中存储大量数据让我感到害怕,但如果你知道你有足够的RAM来做到这一点,也许可以忽略我的建议。如果不能将其变成一个管道,我会将最后五分钟的数据提取到一个临时文件中。 - undefined
2
@hek2mgl的评论中所提到的“显而易见”的意思是,当URL包含破折号时,字节计数不会在$13中。 - undefined
显示剩余4条评论
2个回答

4

不要在冒号上将整行分割。相反,从您提取的字段中删除端口号。

split($1, v, /:/); vHost[v[1]]++; ...

我不明白为什么你要按破折号分割,因为无论如何,字段编号都将被重新编号,所以最终结果可能会是这样的

awk '{ split($1, v, /:/); vHost[v[1]]++; Bytes[v[1]]+=$11 }
   END { for (var in vHost)
        printf "%s %.0f %.0f\n", var, vHost[var], Bytes[var] }'

太好了,谢谢!我之前不知道这是可能的。为了添加的摘要,生成的代码如下:echo "$clog" | awk '{ split($1, v, /:/); vHost[v[1]]+=$11 } END { for (var in vHost) { printf "%s %.0f\n", var, vHost[var] }}' - undefined

0
这个想法是不再担心IPv4和IPv6的问题,而是让FS足够灵活,能够处理它们,而不需要再拆分一个额外的数组v。
mawk 'END { for (_ in __)
                printf("%s %d\n", _, __[_]) } { __[$!_] += $NF }' FS=':.* '

ipv6.com 273
djerk.nl 48394
bogus.com 37262

为了使正则表达式在尾部有额外的双引号字符串时更具弹性,尝试修改`RS` :
gawk 'END { for (_ in __) printf("%s %d\n", _, __[_]) 

        } { __[$!_] += $NF }' FS=':.+ ' RS='([^0-9]*"[^"]*")?\n'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接