3

我有一组邮件日志:mail.log mail.log.0 mail.log.1.gz mail.log.2.gz

这些文件中的每一个都包含按时间顺序排列的行,这些行以时间戳开头,例如:

5月3日13:21:12...

如何使用 bash(和相关命令行工具)在某个日期/时间之后和另一个日期/时间之前轻松获取每个日志条目,而无需比较每一行?请记住,我之前和之后的日期可能与日志文件中的任何条目都不完全匹配。

在我看来,我需要确定第一行的偏移量大于起始时间戳,而最后一行的偏移量小于结束时间戳,并以某种方式将该部分剪掉。

4

6 回答 6

5

将您的最小/最大日期转换为“自纪元以来的秒数”,

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
于 2009-05-06T04:49:20.983 回答
1

你必须查看你想要的范围内的每一行(判断它是否在你想要的范围内)所以我猜你的意思不是文件中的每一行。至少,您必须查看文件中的每一行,包括您范围之外的第一行(我假设这些行是按日期/时间顺序排列的)。

这是一个相当简单的模式:

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

当然,我会编写一个脚本来返回这些行,而不是每次都尝试手动执行。

于 2009-05-06T04:13:09.230 回答
1

这里有一个关于如何做到这一点的基本想法:

  1. 检查文件上的日期戳以查看它是否无关紧要
  2. 如果它可能是相关的,则在必要时解压缩并检查文件的第一行和最后一行以查看它是否包含开始时间或结束时间。
  3. 如果是,请使用递归函数来确定它是否在文件的前半部分或后半部分包含开始时间。使用递归函数,我认为您可以在一百万行日志文件中找到任何日期,并进行大约 20 次比较。
  4. 按从第一个条目的偏移量到最后一个条目的偏移量的顺序回显日志文件(不再进行比较)

我不知道的是:如何最好地读取文件的第 n 行(使用tail n+**n |head 1** 效率如何?)

有什么帮助吗?

于 2009-05-06T12:51:35.147 回答
1

也许你可以试试这个:

sed -n "/BEGIN_DATE/,/END_DATE/p" logfile
于 2011-09-20T14:20:01.873 回答
0

在 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 行),以快速获得接近准确的答案,然后在需要时对其进行改进。这些只是性能改进的一些建议。

于 2009-05-06T04:03:57.020 回答
0

我知道这个线程很旧,但我最近找到了一个满足我需求的单行解决方案后偶然发现了它:

awk -v ts_start="2018-11-01" -v ts_end="2018-11-15" -F, '$1>=ts_start && $1<ts_end' myfile

在这种情况下,我的文件在第一个字段中包含带有逗号分隔值和时间戳的记录。您可以对开始和结束时间戳使用任何有效的时间戳格式,并根据需要替换这些 shell 变量。

如果要写入新文件,只需使用> newfile附加到上述末尾的正常输出重定向 ( )。

于 2018-11-15T17:56:52.423 回答