我们有一个日志文件,我经常用 tail 实时跟踪它,并使用 grep 来过滤我感兴趣的行。但是,这些行包含很多我并不总是感兴趣的数据,但它们对我来说很难解析,这样我只能看到我想要的行的部分。每个行条目的格式主要是列表标签和用引号括起来的数据(有时包含空格)。以下是一些示例(经过清理的)日志行:
2017:11:29-11:29:56 filter-1 httpproxy[3194]: id="0001" severity="info" sys="SecureWeb" sub="http" name="http access" action="pass" method="CONNECT" srcip="10.11.12.13" dstip="14.3.1.4" user="" group="" ad_domain="" statuscode="200" cached="0" profile="REF_HttPro1234 (Campus2)" filteraction="REF_HttStu (Allow Policy)" size="6518" request="0x915a3e00" url="https://website.net/" referer="" error="" authtime="0" dnstime="1" cattime="73" avscantime="0" fullreqtime="61576999" device="0" auth="6" ua="" exceptions="" category="9998" reputation="unverified" categoryname="Uncategorized" country="United States" application="krux" app-id="826"
2017:11:29-11:29:56 filter-1 httpproxy[3194]: id="0001" severity="info" sys="SecureWeb" sub="http" name="http access" action="pass" method="GET" srcip="10.13.14.15" dstip="154.6.75.10" user="" group="" ad_domain="" statuscode="200" cached="0" profile="REF_HttPro1235 (Campus1)" filteraction="REF_HttStu (Allow Policy)" size="3161" request="0x6b4d5610" url="http://host.com/mini_banner.png" referer="http://www.web.com/computers.htm" error="" authtime="0" dnstime="0" cattime="64" avscantime="848" fullreqtime="50046" device="0" auth="6" ua="Mozilla/5.0 (X11; CrOS x86_64 9765.85.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.123 Safari/537.36" exceptions="" category="111" reputation="trusted" categoryname="Education/Reference" sandbox="-" content-type="image/png"
需要注意的一件事是,并非所有标签都出现在每一行上。例如,application 和 app-id 出现在第一行中,但不在第二行中。
使用上面的行作为示例输入,我希望作为输出的示例是仅按顺序显示 srcip、categoryname 和 url 标签。所需的输出将如下所示:
10.11.12.13 Uncategorized https://website.net/
10.13.14.15 Education/Reference http://host.com/mini_banner.png
我正在寻找一种易于调整的解决方案,以便我可以即时调整显示的标签。
答案1
您的数据是高度结构化的键=“值”,因此您可以使用 gnu awk 编写一个小型 shell 脚本,该脚本将键名称列表作为参数并仅打印这些值。例如myscript
:
#!/bin/bash
awk -v lhs="$*" '
BEGIN{ FPAT = "[a-z-]*=\"[^\"]*\""
nwant = split(lhs,want)
}
{ for(i=1;i<=NF;i++){
start = match($i,/([a-z-]*)="([^"]*)"/,a)
key[a[1]] = a[2]
}
for(i=1;i<=nwant;i++){printf "%s ",key[want[i]]; key[want[i]] = ""}
printf "\n"
}'
您称之为myscript srcip categoryname url
.这将 awk 变量设置lhs
为参数作为单个字符串,该字符串want
在开始时被拆分为数组。这些行被 awk 划分为与模式匹配的字段键=“值”通过使用内置FPAT
变量。
在每一行上,对于每个字段,我们将其分为match()
2 个捕获组,分别用于键和双引号中的部分。这些由 awk 放入 array 中a
,我们将它们保存在key
由键字符串索引的关联数组中。
然后,对于每个想要的键,我们打印该值,并清除下一行的值(如果该行没有该键)。显然,这假设所有数据都具有所需的结构,并且需要更改来处理值内的 (") 或具有非字母字符的键。
4.0 之前的 gnu awk (gawk) 版本没有FPAT
内置功能来将行拆分为与模式匹配的字段,因此您必须自己执行此操作:
#!/bin/bash
awk -v lhs="$*" '
BEGIN{ nwant = split(lhs,want) }
{ input = $0
while(match(input,"[a-z-]*=\"[^\"]*\"")>0){
field = substr(input,RSTART,RLENGTH)
input = substr(input,RSTART+RLENGTH)
start = match(field,/([a-z-]*)="([^"]*)"/,a)
key[a[1]] = a[2]
}
for(i=1;i<=nwant;i++){printf "%s ",key[want[i]]; key[want[i]] = ""}
printf "\n"
}'
显然,您可以将两个匹配调用合并为一个,但这显示了与原始匹配的差异。
答案2
使用(符合 POSIX 标准)sed
...
sed 's/.* srcip="\([^"]*\)" .* url="\([^"]*\)" .* categoryname="\([^"]*\)" .*/\1 \3 \2/' logfile
这里没有什么特别的,只需找到键并用括号包围值,\(..\)
这允许它们用作反向引用。然后我们用空格分隔的后向引用替换字符串,并根据您的要求进行排序:\1 \3 \2
。
输出:
10.11.12.13 Uncategorized https://website.net/
10.13.14.15 Education/Reference http://host.com/mini_banner.png
如果日志包含不具有所有这些键的字符串,那么您可以使用:
sed -n 's/.* srcip="\([^"]*\)" .* url="\([^"]*\)" .* categoryname="\([^"]*\)" .*/\1 \3 \2/p' logfile
这只会打印与模式匹配的行。
当然,如果您想以流式传输方式使用它们,只需删除文件名并执行[something sending logs to stdout] | sed ...