在 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
这是脚本的作用:
- 将脚本的第一个参数分配给变量
$search
(要匹配行的文本),第二个参数分配给变量 $log
(日志文件的名称)
- 为
sed
创建正则表达式
(...)
表示分组
\[some text\]
表示方括号内的 some text
(它们用反斜杠转义)
[^...]
表示除 ...
之外的任何字符,即 [^]]
表示除右方括号外的任何字符(正则表达式终止需要)
.+
表示任意字符的任意正数
</code> 表示我们需要使用第一组的文本(见第一个项目符号)</li>
<li><code>sed
的选项-rn
表示分别抑制每行的默认打印和扩展正则表达式的使用
- 在您的日志文件中使用带有 sed 的正则表达式
log.txt
希望对您有所帮助。
这是 perl 中快速而肮脏的单行解决方案:
perl -n -e '$,=" "; @groups = $_ =~ m/(\[.*?\]).*(\[(?:id|uri).*?\]).*((?-2)).*/ ; print @groups, "\n"' /path/to/log/file.log
这里假定第一个字段包含日期。它不需要 id
和 uri
字段的任何特定顺序,但会按照它们在文件中出现的顺序打印它们。
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 看看他们是否有任何特定的解决方案。
我需要 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
这是脚本的作用:
- 将脚本的第一个参数分配给变量
$search
(要匹配行的文本),第二个参数分配给变量$log
(日志文件的名称) - 为
sed
创建正则表达式(...)
表示分组\[some text\]
表示方括号内的some text
(它们用反斜杠转义)[^...]
表示除...
之外的任何字符,即[^]]
表示除右方括号外的任何字符(正则表达式终止需要).+
表示任意字符的任意正数</code> 表示我们需要使用第一组的文本(见第一个项目符号)</li> <li><code>sed
的选项-rn
表示分别抑制每行的默认打印和扩展正则表达式的使用
- 在您的日志文件中使用带有 sed 的正则表达式
log.txt
希望对您有所帮助。
这是 perl 中快速而肮脏的单行解决方案:
perl -n -e '$,=" "; @groups = $_ =~ m/(\[.*?\]).*(\[(?:id|uri).*?\]).*((?-2)).*/ ; print @groups, "\n"' /path/to/log/file.log
这里假定第一个字段包含日期。它不需要 id
和 uri
字段的任何特定顺序,但会按照它们在文件中出现的顺序打印它们。
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 看看他们是否有任何特定的解决方案。