我目前正在尝试使用 awk 从大量日志文件中获取符合特定条件的项目。本质上,我需要能够根据命令中包含的信息(通常可以位于命令的不同位置)提取由事务 ID 标记的整个命令。下面的示例日志(高度浓缩)。请注意,发送的命令可以是单行,也可以分布在多行中(如 00001 和 00002),并且命令不一定组合在一起,它们之间可以散布其他 ID:
(NAME, 486, 00001) <xml><command:name>target</command:name></xml>
(NAME, 486, 00001) <response>
(NAME, 486, 00001) <result code="200">
(NAME, 486, 00001) <msg>Command failed</msg>
(NAME, 486, 00001) </result>
(NAME, 486, 00001) </response>
(FOO, 486, 00002) <xml>
(FOO, 486, 00002) <differentCommand:name>This is another sent command</differentCommand:name></xml>
(FOO, 486, 00002) </xml>
(FOO, 486, 00002) <response>
(FOO, 486, 00002) <result code="400">
(FOO, 486, 00002) <msg>Command completed successfully</msg>
(FOO, 486, 00002) </result>
(FOO, 486, 00002) </response>
(ANOTHERNAME, 486, 00003) <xml><command:name>target</command:name></xml>
(ANOTHERNAME, 486, 00003) <response>
(ANOTHERNAME, 486, 00003) <result code="400">
(ANOTHERNAME, 486, 00003) <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003) </result>
(ANOTHERNAME, 486, 00003) </response>
(FOO, 486, 00004) <xml>
(FOO, 486, 00004) <command:name>This is another sent command</command:name></xml>
(FOO, 486, 00004) </xml>
(FOO, 486, 00004) <response>
(FOO, 486, 00004) <result code="400">
(FOO, 486, 00004) <msg>Command completed successfully</msg>
(FOO, 486, 00004) </result>
(FOO, 486, 00004) </response>
本质上,我想返回整个命令:名称,包括响应(括号中的 5 位数字是事务 ID),但仅限于成功的地方(结果代码 =“400”)。
这是我到目前为止所拥有的:
BEGIN { FS="[(,)]"; }
$4 ~ "<command:name" { id[$3] = $3 }
{ for (i in id) {
if ($3 == i) {
if ($5 ~ "Command completed success")
success[i] = i;
}
}
}
$4 in success { print $0 }
但显然这不会再回去了向上一旦发现成功,即可获取条目的其余部分。它只返回:
(ANOTHERNAME, 486, 00003) <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003) </result>
(ANOTHERNAME, 486, 00003) </response>
(FOO, 486, 00004) <msg>Command completed successfully</msg>
(FOO, 486, 00004) </result>
(FOO, 486, 00004) </response>
我尝试在 BEGIN 语句中放置一个循环,但这需要很长时间,并且在尝试使用其大小的数组时遇到内存问题(这些文件超过 1 GB)。
我希望返回的是:
(ANOTHERNAME, 486, 00003) <xml><command:name>target</command:name></xml>
(ANOTHERNAME, 486, 00003) <response>
(ANOTHERNAME, 486, 00003) <result code="400">
(ANOTHERNAME, 486, 00003) <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003) </result>
(ANOTHERNAME, 486, 00003) </response>
(FOO, 486, 00004) <xml>
(FOO, 486, 00004) <command:name>This is another sent command</command:name></xml>
(FOO, 486, 00004) </xml>
(FOO, 486, 00004) <response>
(FOO, 486, 00004) <result code="400">
(FOO, 486, 00004) <msg>Command completed successfully</msg>
(FOO, 486, 00004) </result>
(FOO, 486, 00004) </response>
我想知道我正在尝试的事情是否可以在 awk 中实现?一段时间以来,我一直在尝试找出使用哪个工具来完成这项任务,据我所知,awk 无疑是最好的(除了必须使用 Python 之外)。速度是我主要关心的问题,只有今天的文件以纯文本格式提供(所以这些文件足够快),但其余的文件都是 gzip 压缩的(所以我正在这样做zcat filename | awk -f test.awk
)-我试图避免多次读取文件,而且它们太大而无法存储在内存中。
答案1
您可以用作</response>
记录结束标记。例如:
$ awk -F'[ )]' '{record[$3] = record[$3] "\n" $0};
/<\/response>/ {
if (record[$3] ~ /completed successfully/) {
# optional: remove leading newline if you don't want
# a blank line before each output record:
# sub(/\n/,"",record[$3])
print record[$3]
};
delete record[$3]
}' input.log
(FOO, 486, 00002) <xml>
(FOO, 486, 00002) <differentCommand:name>This is another sent command</differentCommand:name></xml>
(FOO, 486, 00002) </xml>
(FOO, 486, 00002) <response>
(FOO, 486, 00002) <result code="400">
(FOO, 486, 00002) <msg>Command completed successfully</msg>
(FOO, 486, 00002) </result>
(FOO, 486, 00002) </response>
(ANOTHERNAME, 486, 00003) <xml><command:name>target</command:name></xml>
(ANOTHERNAME, 486, 00003) <response>
(ANOTHERNAME, 486, 00003) <result code="400">
(ANOTHERNAME, 486, 00003) <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003) </result>
(ANOTHERNAME, 486, 00003) </response>
(FOO, 486, 00004) <xml>
(FOO, 486, 00004) <command:name>This is another sent command</command:name></xml>
(FOO, 486, 00004) </xml>
(FOO, 486, 00004) <response>
(FOO, 486, 00004) <result code="400">
(FOO, 486, 00004) <msg>Command completed successfully</msg>
(FOO, 486, 00004) </result>
(FOO, 486, 00004) </response>
这与下面的 sed+perl 和 sed+awk 版本类似,但它通过将每行(前面有换行符)附加到名为 的数组的适当元素(即 id 号)来构造每条记录本身record
。当它看到一行时</response>
,如果它匹配“成功完成”,它就会打印该元素,然后删除该元素。
这将比 sed + awk 或 sed + perl 版本慢一点(因为它必须将每个输入行附加到数组的一个元素sed
- 它比仅仅经常插入一个空行使用更多的CPU资源),并且使用更多的内存(因为每条记录都保留在内存中,直到有</response>
一行),但并不过分......它保持每条记录仅在需要时保留在内存中,然后将其删除。
但是,即使任何给定 id 的记录与其他 id 的记录交错,此版本也应该可以工作。
这是一个 Perl 等效项:
perl -F'[\h)]' -e '
$record{$F[2]} .= $_;
if (/<\/response>/) {
if ($record{$F[2]} =~ /completed successfully/) {
# print blank line between records
print "\n" if $not_first_record++;
print $record{$F[2]}
}
delete $record{$F[2]};
}' input.log
我的测试(使用包含 100,000 个示例数据副本的 120 MB 输入文件)表明 awk 版本的速度几乎是前者的两倍。 awk 版本在我的测试系统(古老的 AMD Phenom II 1090T)上运行大约 4.6 秒,而 perl 版本大约需要 7.4 秒。
更新
这是一个优化的 perl 版本:
它不使用匹配水平空白或右括号 ( [\h)]
) 作为字段分隔符的正则表达式,而是使用 perl 默认的空白分隔。它从第三个字段中提取每个记录的键,然后截掉最后一个字符()
)。
该版本的运行时间约为 3.9 秒,几乎是快的两倍 - 表明使用正则表达式进行-F
自动分割模式时会产生巨大的性能损失。
顺便说一句,我尝试使用索引数组来记录而不是关联数组(即@record
使用数字索引而不是%record
字符串键),但它在性能上没有明显的差异。我还尝试使用该index()
函数而不是正则表达式匹配(index($record{$key},"completed successfully")
而不是$record{$F[2]} =~ /completed successfully/
),但这也没有产生明显的性能差异。
perl -ane '
chop($key = $F[2]);
$record{$key} .= $_;
if (/<\/response>/) {
if ($record{$key} =~ /completed successfully/) {
print "\n" if $not_first_record++;
print $key, $record{$key};
}
delete $record{$key}
}' input.log
同样的优化也提高了 awk 的性能,尽管没有那么显着。
awk 没有chop()
函数,但substr()
可以用来做同样的事情。
awk '{
key = substr($3, 1, length($3)-1);
record[key] = record[key] "\n" $0
};
/<\/response>/ {
if (record[key] ~ /completed successfully/) {
sub(/^\n/,"",record[key])
print record[key]
};
delete record[key]
}' input.log
该版本的运行时间约为 3.5 秒(比之前的 awk 版本的 4.6 秒快约 30%)。
总体而言,更新后的 awk 和 perl 版本在性能上更加接近,但 awk 的速度仍快 12% 左右。
对代码的微小更改可能会导致性能上的巨大差异。
或者:
您的日志条目是否总是像这样用 id 整齐地分隔,或者它们是否与其他 id 交错?
如果它们被整齐地分开,最简单的方法之一是使用sed
通过插入空行将其分成“段落”(即由一个或多个空行分隔)前每<xml>
行。
sed
然后将 的输出通过管道输入awk
或 ,perl
以“段落模式”读取日志。对于 awk,RS=""
在 BEGIN 块中设置(或使用-v
选项),在 perl 中使用-00
命令行选项。然后你的 awk 或 perl 脚本只需要检查记录是否包含“成功完成”。如果是,则打印记录:
这应该比上面的仅 awk 版本运行得明显更快并且使用更少的内存......但只有在记录时才能正确工作不是与其他记录交错。
$ sed '/) <xml>/i\\n' input.log |
perl -00 -ne 'print if /completed successfully/m'
(FOO, 486, 00002) <xml>
(FOO, 486, 00002) <differentCommand:name>This is another sent command</differentCommand:name></xml>
(FOO, 486, 00002) </xml>
(FOO, 486, 00002) <response>
(FOO, 486, 00002) <result code="400">
(FOO, 486, 00002) <msg>Command completed successfully</msg>
(FOO, 486, 00002) </result>
(FOO, 486, 00002) </response>
(ANOTHERNAME, 486, 00003) <xml><command:name>target</command:name></xml>
(ANOTHERNAME, 486, 00003) <response>
(ANOTHERNAME, 486, 00003) <result code="400">
(ANOTHERNAME, 486, 00003) <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003) </result>
(ANOTHERNAME, 486, 00003) </response>
(FOO, 486, 00004) <xml>
(FOO, 486, 00004) <command:name>This is another sent command</command:name></xml>
(FOO, 486, 00004) </xml>
(FOO, 486, 00004) <response>
(FOO, 486, 00004) <result code="400">
(FOO, 486, 00004) <msg>Command completed successfully</msg>
(FOO, 486, 00004) </result>
(FOO, 486, 00004) </response>
或者使用 awk:
sed '/) <xml>/i\\n' input.log | awk -v RS='' '/completed successfully/'
此版本的输出几乎相同,但每个输出记录之间没有空行。
就我个人而言,我发现每个输出记录之间的空行很有用,因为它可以更轻松地在必要时进一步处理输出,因为它已经处于“段落模式”。当然这只是主观偏好。