在 linux 中记录文本解析

log text parsing in linux

我需要 select 日志中的文本并将该字段有一列存储到新文件中。

例如下面是日志格式

[Mon Dec 07] [error] [client 10.0.0.65] [id "981004"] [file "sample"] [line "84"] [hostname "test"] [uri "/login"] [unique_id "VmVddAo"]
[Mon Dec 07] [error] [client 10.0.0.65] [file "sample"] [line "47"] [id "960015"] [rev "1"] [msg "Request Missing an Accept Header"] [severity "NOTICE"][ver "OWASP_CRS/2.2.9"] [maturity "9"] [accuracy "9"] [tag "MISSING_HEADER_ACCEPT"] [tag "WASCTC/WASC-21"] [tag "OWASP_TOP_10/A7"] [tag "PCI/6.5.10"] [hostname "test"] [uri "/home"] [unique_id "VmVddQo"]

想要像下面这样打印输出

[Mon Dec 07] [id "981004"] [uri "/login"]
[Mon Dec 07] [id "960015"] [uri "/home"]

我已经使用 awk 按列打印

grep "Mon Dec 07" filename | sed '/\[[a-zA-Z]/\t&/g' | awk -F'\t' '{print }'

But i got the below output 

[id "981004"]
[file "sample"]

Because the column are found on different places, for example

[id "981004"] in the 4th column
[id "960015"] in the 6th column 

如何使用 like 获取值,id 作为键,双引号内是该键的值。 selecting 所有值后,它必须作为列存储在新文件 (csv) 中。

感谢 vrs 和 Mirosław Zalewski

#!/bin/bash

search=
log=
regexp="s/(\[$search[^]]*\]).+(\[id[^]]*\]).+(\[uri[^]]*\]).+/  /p"
sed -rn "$regexp" 

&

perl -n -e '$,=" "; @groups = $_ =~ m/(\[.*?\]).*(\[(?:id|uri).*?\]).*((?-2)).*/ ; print @groups, "\n"' /path/to/log/file.log

都有效...

你可以用这样的脚本来做到这一点:

#!/bin/bash

search=
log=
regexp="s/(\[$search[^]]*\]).+(\[id[^]]*\]).+(\[uri[^]]*\]).+/  /p"
sed -rn "$regexp" 

您可以将此程序保存到文件中(例如,script.sh),使其可执行(chmod +x script.sh)和运行 $ ./script.sh "Mon Dec 07" log.txt

这是脚本的作用:

  1. 将脚本的第一个参数分配给变量 $search(要匹配行的文本),第二个参数分配给变量 $log(日志文件的名称)
  2. sed 创建正则表达式
    • (...)表示分组
    • \[some text\] 表示方括号内的 some text(它们用反斜杠转义)
    • [^...] 表示除 ... 之外的任何字符,即 [^]] 表示除右方括号外的任何字符(正则表达式终止需要)
    • .+表示任意字符的任意正数
    • </code> 表示我们需要使用第一组的文本(见第一个项目符号)</li> <li><code>sed的选项-rn表示分别抑制每行的默认打印和扩展正则表达式的使用
  3. 在您的日志文件中使用带有 sed 的正则表达式 log.txt

希望对您有所帮助。

这是 perl 中快速而肮脏的单行解决方案:

perl -n -e '$,=" "; @groups = $_ =~ m/(\[.*?\]).*(\[(?:id|uri).*?\]).*((?-2)).*/ ; print @groups, "\n"' /path/to/log/file.log

这里假定第一个字段包含日期。它不需要 iduri 字段的任何特定顺序,但会按照它们在文件中出现的顺序打印它们。


Perl 中的另一个更灵活、更少脏和多行的解决方案:

%seeked = map { $_ => 1 } qw(id uri unique_id severity msg);

while (<>) {
    my $string = $_;
    my $closing = 1;
    while ( $closing != -1 ) {
            $closing = index($string, "]");
            $field = substr($string, 0, $closing+1);
            $field =~ s/^\s+|\s+$//g;
            $string = substr($string, $closing + 1);

            my @content = split(/ /, $field, 4);

            if (scalar @content == 3 and $field !~ m/"/) {
                    print $field . " ";
                    next;
            }

            if ($seeked{ substr($content[0], 1) }) {
                    print $field . " ";
            }
    }
    print "\n";
}

要使用它,请复制该代码,将其粘贴到文件中并另存为 whatever.pl。然后,在 shell 中键入:

perl /path/to/whatever.pl /path/to/log/file.log

在第一行代码中,紧跟在 qw 之后的括号内,您声明了要打印的字段。如果您需要其他字段或不需要其中的某些字段,请修改该行。

此解决方案仍然无法按预先指定的顺序打印字段,也无法打印一些字符串来代替缺少的可选字段。为此,您需要一个解析器 - 首先将字段放入某些数据结构中,然后在行尾打印检测到的字段。

此外,"date-recognizing" 代码是 Very Wrong™。它检查字段是否恰好包含两个空格并且不包含双引号字符。如果它符合这两个条件,它会假定该字段是日期并打印它。正确的解决方案是检查字符串是否代表有效日期。这可以通过 Date::Manip CPAN 来完成 模块,但需要单独安装。


如果您想以任意顺序打印任意字段,您需要为此文件格式编写自定义(?)解析器。这不应该太难,但有一些陷阱需要寻找:

  • 日期字段具有隐式键
  • 类型字段具有隐式键
  • 至少"client"字段不引用值
  • 很多字段是可选的
  • 某些字段可以出现多次(至少 "tag")

我认为 CSV 格式不适合此类数据。最适合二维数据结构。因为您的数据具有可选字段和存储列表的字段,所以更灵活的东西 - 如 XML 或 JSON - 会更好。

附带说明一下,似乎编写这些日志的人都认为阅读它们很容易。我会建议检查供应商 documentation/support 看看他们是否有任何特定的解决方案。