我正在编写一个 ksh 脚本来解析日志文件并在发现重要消息时发送电子邮件。有些消息是信息性的,我想忽略它们。
日志文件有格式
2018-01-24.08.24.35.875675 some text
more text
more text
more text
more text
2018-01-24.08.24.37.164538 some text
more text
more text
INF9999W <-- informational text
more text
2018-01-24.08.24.46.8602545 some text
more text
more text
more text
时间戳将被视为消息分隔符,时间戳属于其后的消息。我想在文件中搜索每次出现的“信息文本”,然后从文件中删除整个消息(从前一个时间戳到下一个时间戳之前)。
如何轻松确定前后时间戳的行号,以便我使用以下命令删除这些行:
awk 'NR<'$preceding_ts' || NR >='$following_ts'
我的方法是将所有时间戳行放入一个文件中,然后循环该文件,直到找到“信息文本”行 # 之前和之后的时间戳行。看起来工作量很大,尤其是在处理大文件时。有没有更有效的方法。
integer inf_line
integer last_ts_line
integer cur_ts
cp $error_log $copy_log
while true
do
inf_line=$(grep -n "INF99999W" $copy_log | head -1 | cut -f1 -d":")
if [[ $inf_line -eq 0 ]]
then
break
fi
grep -n -E "^20[0-9][0-9]-[0-1][0-9]-[0-3][0-9]-" $copy_log | cut -f1 -d":" > $ts_lines
last_ts_line=99999999
cat $ts_lines | while read cur_ts
do
if [[ $cur_ts -gt $inf_line && $last_ts_line -lt $inf_line ]]
then
awk 'NR<'$last_ts_line' || NR >='$cur_ts'' $copy_log > $temp_log
cp $temp_log $copy_log
last_ts_line=$cur_ts
break
fi
last_ts_line=$cur_ts
done
if [[ $last_ts_line -lt $inf_line ]]
then
awk 'NR<'$last_ts_line'' $copy_log > $temp_log
cp $temp_log $copy_log
fi
done
谢谢。
答案1
我会通过存储当前消息的行来实现它,当消息结束时,如果没有INF
看到标记,则打印存储的批次。这里,d
保存当前消息的行(d 代表数据),p
告诉我们是否要打印存储的行。
awk -vinfo='INF99+' \
'/^20[0-9][0-9]-[0-1][0-9]-[0-3][0-9]/ {
if (p) printf "%s", d; d = $0 ORS; p=1; next }
$0 ~ info {p=0}
{d = d $0 ORS}
END {if (p) printf "%s", d}' < log
这里的第一条规则匹配时间戳行,如果p
为 true,则打印任何存储的行,存储该行并设置p
为 1。如果看到p
具有该模式的线,则第二条规则将重置为零;info
模式设置为变量-vinfo=...
。第三条规则将当前行附加到收集的行中,并且该END
规则再次仅打印收集的行(如果p
设置)。
我们也可以这样写,这也会检查info
时间戳行上的模式:
awk -vinfo='INF99+' \
'/^20[0-9][0-9]-[0-1][0-9]-[0-3][0-9]/ {
if (p) { printf "%s", d }; d = ""; p=1; }
$0 ~ info {p=0}
{d = d $0 ORS}
END {if (p) printf "%s", d}' < log
一般来说,用awk
Perl 或 Perl 编写这样的东西可能是个好主意。结果至少比 fork grep
、awk
、cut
等的数十个副本的 shell 脚本运行得快得多......