如何使用 bash (grep/sed/etc) 获取 2 个时间戳之间的日志文件部分?
题
我有一组邮件日志:mail.log mail.log.0 mail.log.1.gz mail.log.2.gz
每个文件都包含按时间顺序排序的行,这些行以时间戳开头,例如:
5 月 3 日 13:21:12 ...
如何轻松获取某个日期/时间之后和另一个日期/时间之前的每个日志条目 使用bash (以及相关的命令行工具)而不比较每一行?请记住,我的之前和之后日期可能与日志文件中的任何条目不完全匹配。
在我看来,我需要确定第一行的偏移量大于开始时间戳,最后一行的偏移量小于结束时间戳,并以某种方式剪切该部分。
解决方案 2
这里有一个如何做的基本想法:
- 检查文件上的日期戳以查看它是否无关紧要
- 如果可能相关,请在必要时解压缩并检查文件的第一行和最后一行,看它是否包含开始或结束时间。
- 如果是,请使用递归函数来确定它是否包含文件的第一个或第二个半部分中的开始时间。使用递归函数我认为你可以在百万行日志文件中找到约20个比较的任何日期。
- 按照从第一个条目的偏移量到最后一个条目的偏移量的顺序回显日志文件(不再进行比较) 醇>
我不知道的是:如何最好地阅读文件的第n行(使用尾部n + ** n |头部1 **的效率如何?)
任何帮助?
其他提示
将您的最小/最大日期转换为“自纪元以来的秒数”,
MIN=`date --date="$1" +%s`
MAX=`date --date="$2" +%s`
将每个日志行中的第一个 n
字转换为相同的
L_DATE=`echo $LINE | awk '{print $1 $2 ... $n}'`
L_DATE=`date --date="$L_DATE" +%s`
比较并丢弃线条,直至到达 MIN
,
if (( $MIN > $L_DATE )) ; then continue ; fi
比较并打印行,直至到达 MAX
,
if (( $L_DATE <= $MAX )) ; then echo $LINE ; fi
超过 MAX
时退出。
if (( $L_DATE > $MAX )) ; then exit 0 ; fi
整个脚本 minmaxlog.sh 看起来像这样,
#!/usr/bin/env bash
MIN=`date --date="$1" +%s`
MAX=`date --date="$2" +%s`
while true ; do
read LINE
if [ "$LINE" = "" ] ; then break ; fi
L_DATE=`echo $LINE | awk '{print $1 " " $2 " " $3 " " $4}'`
L_DATE=`date --date="$L_DATE" +%s`
if (( $MIN > $L_DATE )) ; then continue ; fi
if (( $L_DATE <= $MAX )) ; then echo $LINE ; fi
if (( $L_DATE > $MAX )) ; then break ; fi
done
我在这个文件 minmaxlog.input ,
上运行它May 5 12:23:45 2009 first line
May 6 12:23:45 2009 second line
May 7 12:23:45 2009 third line
May 9 12:23:45 2009 fourth line
June 1 12:23:45 2009 fifth line
June 3 12:23:45 2009 sixth line
像这样,
./minmaxlog.sh "May 6" "May 8" < minmaxlog.input
你必须查看你想要的范围内的每一行(以判断它是否在你想要的范围内),所以我猜你的意思不是文件中的每一行。至少,您必须查看文件中的每一行,直到并包括超出范围的第一行(我假设这些行按日期/时间顺序排列)。
这是一个相当简单的模式:
state = preprint
for every line in file:
if line.date >= startdate:
state = print
if line.date > enddate:
exit for loop
if state == print:
print line
如果需要的话,您可以用 awk、Perl、Python 甚至 COBOL 编写,但逻辑始终相同。
首先找到行号(用 grep ),然后盲目地打印出该行范围不会有帮助,因为 grep 还必须查看所有行(全部 其中,不仅仅是范围之外的第一个,而且很可能 两次, ,一个用于第一行,一个用于最后一行)。
如果这是您经常要做的事情,您可能需要考虑将工作量从“每次执行”转移到“文件稳定后一次”。一个示例是将日志文件行加载到数据库中,并按日期/时间索引。
这需要一些时间来设置,但会导致您的查询变得更快。我不一定提倡使用数据库 - 您可能可以通过将日志文件拆分为每小时日志来达到相同的效果:
2009/
01/
01/
0000.log
0100.log
: :
2300.log
02/
: :
然后在给定的时间内,您确切地知道从哪里开始和停止寻找。范围 2009/01/01-15:22
通过 2009/01/05-09:07
会导致:
- 文件的一些(最后一位)
2009/01/01/1500.txt
. - 所有文件
2009/01/01/1[6-9]*.txt
. - 所有文件
2009/01/01/2*.txt
. - 所有文件
2009/01/0[2-4]/*.txt
. - 所有文件
2009/01/05/0[0-8]*.txt
. - 文件的一些(第一位)
2009/01/05/0900.txt
.
当然,我会编写一个脚本来返回这些行,而不是每次都尝试手动执行。
也许你可以试试这个:
sed -n "/BEGIN_DATE/,/END_DATE/p" logfile
在 Bash 环境中这可能是可能的,但您确实应该利用具有更多内置支持来处理字符串和日期的工具。例如,Ruby 似乎具有解析日期格式的内置功能。然后它可以将其转换为易于比较的 Unix 时间戳(一个正整数,表示自纪元以来的秒数)。
irb> require 'time'
# => true
irb> Time.parse("May 3 13:21:12").to_i
# => 1241371272
然后您可以轻松编写 Ruby 脚本:
- 提供开始和结束日期。将它们转换为这个 Unix 时间戳数字。
- 逐行扫描日志文件,将日期转换为其 Unix 时间戳,并检查它是否在开始日期和结束日期的范围内。
笔记:首先转换为 Unix Timestamp 整数很好,因为比较整数非常容易且高效。
您提到“没有比较每一行。”很难在日志文件中的位置上“猜测”条目开始太旧,或者太新的地方而不检查之间的所有值。但是,如果确实存在单调增加的趋势,那么您立即知道何时停止解析行,因为一旦下一个条目太新(或太旧,取决于数据的布局),您就知道可以停止搜索。尽管如此,仍然存在找到所需范围内的第一行的问题。
我刚刚注意到你的编辑。我要说的是:
如果你是 真的 担心有效地找到开始和结束条目,那么您可以对每个条目进行二分搜索。或者,如果这对于 bash 工具来说似乎太过分或太困难,您可以启发式地只读取 5% 的行(每 20 行中就有 1 行),以快速获得接近精确的答案,然后根据需要进行完善。这些只是性能改进的一些建议。
我知道这个帖子已经老了,但我最近在找到满足我需求的单行解决方案时偶然发现了它:
awk -v ts_start="2018-11-01" -v ts_end="2018-11-15" -F, '$1>=ts_start && $1<ts_end' myfile
在这种情况下,我的文件包含逗号分隔值的记录和第一个字段中的时间戳。您可以使用任何有效的时间戳格式作为开始和结束时间戳,并根据需要替换这些shell变量。
如果要写入新文件,只需使用附加到上面末尾的正常输出重定向(&gt; newfile
)。