我需要从日志文件中提取某些字符串,尽管这些字符串不在标准化字段/列中。例如:
date="2017-01-03 08:30:02 -0500",fac=f_kernel_ipfilter,area=a_general_area,type=t_attack,pri=p_major,hostname=hostname,category=policy_violation,event="ACL deny",attackip=1.1.1.1,attackzone=internal,app_risk=low,app_categories=remote-admin,netsessid=c550e586ba75a,src_geo=US,srcip=1.1.1.1,srcport=38256,srczone=internal,protocol=6,dst_geo=US,dstip=2.2.2.2,dstport=80,dstzone=external,rule_name=Deny_All,reason="Traffic denied by policy.",application=SSH
我想获取 srcip、srczone、protocol、dstip、dstzone、dstport 和rule_name。我目前使用 perl 惰性匹配来删除我不需要的 OUT 字段。有没有办法只获取这 8 个字符串和逗号内的数据,,dstport=80,
无论日志文件中的位置如何?同一数据有许多不同的条目位置,这使得这变得很困难。
答案1
这是一种快速但肮脏的方法,使用perl
:
$ perl -F, -lane '@l = grep {/srcip|srczone|protocol|dstip|dstzone|dstport|rule_name/} @F;
print join ",",@l' file
srcip=1.1.1.1,srczone=internal,protocol=6,dstip=2.2.2.2,dstport=80,dstzone=external,rule_name=Deny_All
这-a
使得 perl 的行为类似于 awk,并根据 给定的字符分割其输入行-F
,并将它们保存为数组的元素@F
。然后,我们grep
创建数组并在数组中保留与目标单词匹配的元素@l
,最后@l
使用逗号进行打印连接。
请注意,如果您的任何模式可以是子模式(假设您有foo=bar
和foobar=baz
),则此操作将会失败。
对于较长的目标模式列表(假设您不想编写实际的脚本),您可以将它们存储在数组中并将它们连接起来|
以创建 grep 的正则表达式。通过\b
在每个模式周围添加,您也可以防止匹配子模式。如果我们还删除不必要的临时数组,我们会得到:
$ perl -F, -lane '
BEGIN{
$pat="\\b" . join "\\b|",qw(srcip= srczone= protocol= dstip= dstzone= dstport= rule_name=)
} print join ",",grep {/$pat/}@F' file
srcip=1.1.1.1,srczone=internal,protocol=6,dstip=2.2.2.2,dstport=80,dstzone=external,rule_name=Deny_All
我们的常驻专家表示,无论 sed awk 或 [. 。 ..] 从你的命令
抱歉,但这显然是荒谬的。以下是在每个工具中执行此操作的一种(多种)方法:
Bourne(再次)外壳。不要使用这个,我只是展示它来证明它是可能的。
$ pat=(srcip= srczone= protocol= dstip= dstzone= dstport= rule_name=); $ o=""; while IFS=, read -a fields; do for f in "${fields[@]}"; do for pat in "${pat[@]}"; do [[ $f =~ $pat ]] && o="$f,$o" done done done < file ; echo ${o%,}
awk
将目标模式保存在文件中:
$ cat patterns srcip srczone protocol dstip dstzone dstport rule_name
然后:
$ awk -F, '(NR==FNR){ pat[$0]++; next; } { for(i=1;i<=NF;i++){ split($i,a,"="); if(a[1] in pat){ printf "%s=%s,",a[1],a[2] } } print "" }' patterns file | sed 's/,$//' srcip=1.1.1.1,srczone=internal,protocol=6,dstip=2.2.2.2,dstport=80,dstzone=external,rule_name=Deny_All
sed
(和外壳)$ pat=(srcip= srczone= protocol= dstip= dstzone= dstport= rule_name=); $ for p in ${pat[@]}; do sed -E "s/.*($p[^,]*).*/\1/" file; done | sed ':a;N;$!ba;s/\n/,/g' srcip=1.1.1.1,srczone=internal,protocol=6,dstip=2.2.2.2,dstzone=external,dstport=80,rule_name=Deny_All
Bourne shell(或任何 POSIX shell)+ sed(至于 1.,不要这样做,这是可能的,但很愚蠢)
$ set srcip= srczone= protocol= dstip= dstzone= dstport= rule_name= $ for f in "$@"; do sed "s/.*\($f[^,]*\).*/\1/" file; done | sed ':a;N;$!ba;s/\n/,/g' srcip=1.1.1.1,srczone=internal,protocol=6,dstip=2.2.2.2,dstzone=external,dstport=80,rule_name=Deny_All
答案2
GNU 的解决方案awk
:
gawk -v OFS= -v FPAT=',(srcip|srczone|protocol|dstip|dstzone|dstport|rule_name)=[^,]*' -e 'NF > 0 { $1=$1; print }'
在这里,我使用了 GNU 特有的功能awk
:对于FPAT
变量,我使用正则表达式指定字段的格式,以便与正则表达式匹配的行的每个部分都分配给$1...$n
.然后我分配$1
给$1
,以便$0
仅使用$1...$n
.
答案3
我对此有点延迟,但会提供一个建议 - 这种数据非常适合map
哈希:
#!/usr/bin/env perl
use strict;
use warnings;
#for debugging - can be removed;
use Data::Dumper;
my @fields = qw ( srcip srczone protocol dstip dstzone dstport rule_name );
#read STDIN or files specified on command line (just like grep/sed/awk)
while ( <> ) {
#split commas
#then read key-value pairs.
my %row = map { m/(.*)=(.*)/ } split /,/;
#for debugging:
print Dumper \%row;
#print fields tab-separated and in order as above.
print join "\t", @row{@fields};
}
oneliner-ify 稍微困难一点,因为你有一个字段列表需要拼写。但:
perl -lane -F, 'BEGIN { @k = qw ( srcip srczone protocol dstip dstzone dstport rule_name ) } %r = map { m/(.*)=(.*)/ } @F; print join "\t", @r{@k}'
答案4
巴什
IFS=, read -r -a fields <<< "$date"
results=()
for keyval in "${fields[@]}"; do
IFS='=' read -r key value <<< "$keyval"
case $key in
srcip|srczone|protocol|dstip|dstzone|dstport|rule_name) results+=("$keyval")
esac
done
(IFS=,; echo "${results[*]}")
srcip=1.1.1.1,srczone=internal,protocol=6,dstip=2.2.2.2,dstport=80,dstzone=external,rule_name=Deny_All